Create a Simple REST API with DENO and OAK framework
Tags: deno, node, webdev, javascript
Published: 2020-05-24 18:00:30

<h1>About Deno</h1> 
<p>
<b>Deno</b> has just recently released version 1.0.0 and this is a new secure runtime for JavaScript and Typescript. 

Main key features of <b>Deno</b> are:
<li>Secure by default. No file, network, or environment access, unless explicitly enabled.</li>
<li>Supports TypeScript out of the box.</li>
<li>Ships only a single executable file.</li>
<li>Has built-in utilities like a dependency inspector (deno info) and a code formatter (deno fmt).</li>
<li>Has a set of reviewed (audited) standard modules that are guaranteed to work with Deno: deno.land/std</li>

If you’d like to learn more about Deno, please check out <a href="https://deno.land/">Deno’s landing page.</a>
</p>
<h2>About Oak</h2>
<p><b>Oak</b> is middleware framework for Deno's http server, including a router middleware.

This middleware framework is inspired by Koa and middleware router inspired by koa-router. For more Info check out <a href="https://github.com/oakserver/oak">here</a>
</p>

<h1>Let's start</h1>
<p>I'll be installing Deno using Brew.</p>
<div class="highlight"><pre class="highlight javascript">
brew install deno
</pre></div>
<p>To verify if the Deno is installed or not. Just type <code>deno --version</code> on your terminal and it will show the install version of Deno.</p>
<div class="highlight"><pre class="highlight">
$ Deno --version
deno 1.0.0
v8 8.4.300
typescript 3.9.2
</pre></div>

<p>After the installation steps, let's create a directory for our application</p>
<div class="highlight"><pre class="highlight javascript">
mkdir denoRestApi <span class="o">&&</span> cd denoRestApi
</pre></div>

<p>We are going to develop a Crud REST api with the following structure</p>

<div class="highlight"><pre class="highlight javascript">
- src
  - controllers
    - dinosaurs.ts
  - models
    - dinosaurs.ts
  - routes
    - routes.ts
  - types
    - types.ts
- server.ts
</pre></div>

<p>
<ul>
<li><b>Controllers</b> : have a logic of the application and handle the client requests.</li>
<li><b>Models</b> : contain the model definition.</li>
<li><b>Routes</b> : containing API routes.</li>
<li><b>Types</b> : contain the types used by model and application responses.</li>
<li><b>Server</b> : code to run localhost server.</li>
</ul>
</p>

<p>Now let’s create our <b>server.ts</b> file in the root of our directory :
</p>

```javascript
import { Application } from "https://deno.land/x/oak/mod.ts";
import router from "./src/routes/routes.ts";

const port = 9000;
const app = new Application();

app.use(router.routes());
app.use(router.allowedMethods());

console.log(`Server running on port ${port}`);
await app.listen({ port });
```
 
<p>The Application class wraps the serve() function from the http package. It has two methods: .use() and .listen(). Middleware is added via the .use() method and the .listen() method will start the server and start processing requests with the registered middleware.
</p>
<p>The middleware is processed as a stack, where each middleware function can control the flow of the response. When the middleware is called, it is passed a <a href="https://github.com/oakserver/oak#context">context</a> and reference to the "next" method in the stack.</p>

<p>Our Next step is to create our endpoints in our <b>routes.ts</b> :</p>

```javascript
import { Router } from "https://deno.land/x/oak/mod.ts";
import {
  getDinosaur,
  getDinosaurs,
  addDinosaur,
  updateDinosaur,
  deleteDinosaur,
} from "../controllers/dinosaurs.ts";

const router = new Router();

router.get("/api/v1/dinosaurs", getDinosaurs)
  .get("/api/v1/dinosaurs/:id", getDinosaur)
  .post("/api/v1/dinosaurs", addDinosaur)
  .put("/api/v1/dinosaurs/:id", updateDinosaur)
  .delete("/api/v1/dinosaurs/:id", deleteDinosaur);

export default router;
```

<p>One of TypeScript’s core principles is that type checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural subtyping”. In TypeScript, interfaces fill the role of naming these types, and are a powerful way of defining contracts within your code as well as contracts with code outside of your project. See below <b> types.ts :</b>
</p>

```javascript
 export interface Dinosaur {
   id: string;
   name: string;
   era: string;
   area: string;
   diet: string;
 }
```

<p>Properties inspired by <a href="https://www.nhm.ac.uk/discover/dino-directory/name/a/gallery.html">here</a></p>

<p>Now let's create our initial list with dinosaurs in <b>models/dinosaurs.ts :</b>
</p>

```javascript
import { Dinosaur } from "../types/types.ts";

export const Dinosaurs: Array<Dinosaur> = [
  {
    id: "1",
    name: "Achillobator",
    era: "Late Cretaceous",
    area: "Mongolia",
    diet: "carnivorous",
  },
  {
    id: "2",
    name: "Agilisaurus",
    era: "Late Jurassic",
    area: "China",
    diet: "herbivorous",
  },
  {
    id: "3",
    name: "Melanorosaurus",
    era: "Late Triassic",
    area: "South Africa",
    diet: "omnivorous",
  },
];
```

<p>After we have created our Dinosaur Interface, our dinosaur list and our routes, let's continue with our methods for each endpoint. <b>controllers/dinosaurs.ts :</b>
</p>

```typescript
import { v4 } from "https://deno.land/std/uuid/mod.ts";
import {
  Dinosaur,
} from "../types/types.ts";
import { Dinosaurs } from "../models/dinosaurs.ts";

const getDinosaurs = ({ response }: { response: any }) => {
  response.body = {
    success: true,
    data: Dinosaurs,
  };
};

const getDinosaur = (
  { params, response }: { params: { id: string }; response: any },
) => {
  const selectedDino: Dinosaur | undefined = Dinosaurs.find((dino) =>
    dino.id === params.id
  );
  if (selectedDino) {
    response.status = 200;
    response.body = {
      success: true,
      data: selectedDino,
    };
  } else {
    response.status = 404;
    response.body = {
      success: false,
      msg: "Dinosaur Not Found",
    };
  }
};

const addDinosaur = async (
  { request, response }: { request: any; response: any },
) => {
  if (!request.hasBody) {
    response.status = 400;
    response.body = {
      success: false,
      msg: "No data",
    };
  } else {
    const { value : dinosaurBody } = await request.body();
    const dinosaur: Dinosaur = dinosaurBody;
    dinosaur.id = v4.generate();
    Dinosaurs.push(dinosaur);
    response.status = 201;
    response.body = {
      success: true,
      data: dinosaur,
    };
  }
};

const deleteDinosaur = (
  { params, response }: { params: { id: string }; response: any },
) => {
  const filteredDinosaurs: Array<Dinosaur> = Dinosaurs.filter(
    (dinosaur: Dinosaur) => (dinosaur.id !== params.id),
  );
  if (filteredDinosaurs.length === Dinosaurs.length) {
    response.status = 404;
    response.body = {
      success: false,
      msg: "Not found",
    };
  } else {
    Dinosaurs.splice(0, Dinosaurs.length);
    Dinosaurs.push(...filteredDinosaurs);
    response.status = 200;
    response.body = {
      success: true,
      msg: `Dinosaur with id ${params.id} has been deleted`,
    };
  }
};

const updateDinosaur = async (
  { params, request, response }: {
    params: { id: string };
    request: any;
    response: any;
  },
) => {
  const requestedDinosaur: Dinosaur | undefined = Dinosaurs.find(
    (dinosaur: Dinosaur) => dinosaur.id === params.id,
  );
  if (requestedDinosaur) {
    const { value : updatedDinosaurBody } = await request.body();
    const updatedDinosaurs: Array<Dinosaur> = Dinosaurs.map(
      (dinosaur: Dinosaur) => {
        if (dinosaur.id === params.id) {
          return {
            ...dinosaur,
            ...updatedDinosaurBody,
          };
        } else {
          return dinosaur;
        }
      },
    );

    Dinosaurs.splice(0, Dinosaurs.length);
    Dinosaurs.push(...updatedDinosaurs);
    response.status = 200;
    response.body = {
      success: true,
      msg: `Dinosaur id ${params.id} updated`,
    };
  } else {
    response.status = 404;
    response.body = {
      success: false,
      msg: `Not Found`,
    };
  }
};

export {
  updateDinosaur,
  deleteDinosaur,
  getDinosaurs,
  getDinosaur,
  addDinosaur,
};
```

<h2>Run application</h2>

```
Deno  run --allow-net  server.ts
```

<h2>Request with curl</h2>
<p>
<img src="https://dev-to-uploads.s3.amazonaws.com/i/c5k9zi06q0ie7fabgd1s.png"  />
</p>

<h2>Resolution</h2>

<p>We've created a dead simple, readable rest api with few lines of code. If you noticed we did not used any Node_modules dependancies instead Deno has an amazing list of features at <a href="https://deno.land/std">Standard Library</a> and <a href="https://deno.land/x">Third-Party Modules</a>. I like Deno so far and I am very excited about the out-of-box tools it provides.</p>

<p>You can find my repo <a href="https://github.com/NickolasBenakis/JurassicPark">here</a>.
Leave a 🌟 if you liked it.
</p>

Thanks a lot,
Don't hesitate to write any comments below, I would love to answer.

Feel free to connect on:
<ul><li><a href="https://github.com/NickolasBenakis">Github</a></li>
<li><a href="https://twitter.com/nickolasbenakis">Twitter</a></li>
<li><a href="https://www.linkedin.com/in/nickolas-benakis-a718baa8/">LinkedIn</a></li>
</ul>