Create a Simple REST API with Deno
Tags: deno, javascript, node, webdev
Published: 2020-05-21 23:42:14

## Intro
Deno has recently hit Version 1, and there’s been a ton of talk about it.

In this post, we won't discuss the ins and outs of Deno. Those resources already exist. If you’d like to learn more about Deno, please check out [Deno’s landing page](https://deno.land/).

What we will do in this post, however, is build a very simple REST API to keep track of to-dos using both Deno and Oak.

## Set up

Let's start by creating our project folder.
```bash
mkdir deno_oak_rest_api_example && cd deno_oak_rest_api_example
```

Follow the steps on the landing page to install Deno.

I'll be installing Deno using Brew.
```bash
brew install deno
```

Pay attention to the terminal. You might encounter some extra setup steps.

Let's install some Deno modules that will help us during our development.

**Denon** is a way for us to have the server reset after every change. This is similar to how we would use Nodemon in Node development.
```bash
deno install -Af --unstable https://deno.land/x/denon/denon.ts
```

After Denon is installed we can start creating our folder structure
```
- src
  - controllers
    - todos.ts
  - model
    - Todos.ts
  - types
    - Todo.ts
- .denon.json
- server.ts
```


Now let's create a `.denon.json` file in the root of our directory.
```javascript
{
    "files": ["server.ts"],
    "quiet": false,
    "debug": true,
    "fullscreen": true,
    "extensions": [".js", ".ts", ".json"],
    "interval": 500,
    "watch": ["src/"],
    "deno_args": ["--allow-net"],
    "execute": {
        ".js": ["deno", "run"],
        ".ts": ["deno", "run"]
    },
    "fmt": false,
    "test": true
}
```

Now let’s create our `server.ts` file in the root of our directory.
```javascript
import { Application, Router } from "https://deno.land/x/oak/mod.ts";

const router = new Router();
const port = 3000;

// Our routes
router.get("/", (context) => {
    context.response.body = "Hello world!";
})

const app = new Application();

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

console.log(`Listening on port ${port}...`);

await app.listen({ port });
```

Run `denon` in the terminal and navigate to path `http://localhost:3000/`. You should see the response: "Hello world!"

Once the basic setup is complete, we can start making our REST API.

## Working on the core API
Let's start by defining the types of items we’re going to be returning through our API.

`./src/types/Todo.ts`
```javascript
export type Todo = {
  id: string;
  name: string;
  isComplete: boolean;
};
```

Then let’s create a model we can use to store our to-dos. In this example we’re using a simple array, but you can replace it with any database.

`./src/model/Todos.ts`
```javascript
import { Todo } from "../types/Todo.ts";

export const Todos: Array<Todo> = [
  {
    id: "40238v4ff-3120-3794-6830-dfgkj49gj30df",
    name: "Update resume",
    isComplete: false,
  },
  {
    id: "3kd90dj2d-7059-5820-1258-goi34j09dfg3",
    name: "Clean room",
    isComplete: false,
  },
  {
    id: "j30vh293g-0192-5832-1082-gj30fj305mc",
    name: "Play games with friends",
    isComplete: true,
  },
  {
    id: "a0s9qk2df90-8069-7069-2130-fj30dkfgh",
    name: "Play guitar",
    isComplete: false,
  },
];
```

Now let’s create the functions at the endpoints will be using.

`./src/contollers/todo.ts`
```javascript
import { v4 } from "https://deno.land/std/uuid/mod.ts";

import { Todos } from "../model/Todos.ts";
import { Todo } from "../types/Todo.ts";

// Helper functions
const findTodo = (todoId: string): Todo | undefined =>
  Todos.find(({ id }) => id === todoId);

const isMissing = (value: any) => value === null || value === undefined;

// Route functions
export const getTodos = ({ response }: { response: any }) => {
  response.status = 200;
  response.body = { msg: "Todos fetched!", data: Todos };
  return;
};

export const getTodo = async ({
  params,
  response,
}: {
  params: any;
  response: any;
}) => {
  const todo: Todo | undefined = findTodo(params.id);

  if (isMissing(todo)) {
    response.body = { msg: "Todo not found!" };
    response.status = 404;
    return;
  }

  response.body = { msg: "Todo fetched!", data: todo };
  response.status = 200;
};

export const addTodo = async ({
  request,
  response,
}: {
  request: any;
  response: any;
}) => {
  const body = await request.body();
  const { name } = await JSON.parse(body.value);

  if (isMissing(name)) {
    response.body = { msg: "Name is missing from the request body" };
    response.status = 400;
    return;
  }

  const newTodo: Todo = {
    id: v4.generate(),
    name,
    isComplete: false,
  };

  Todos.push(newTodo);

  response.body = { msg: "Todo added!", data: newTodo };
  response.status = 200;
};

export const updateTodo = async ({
  params,
  request,
  response,
}: {
  params: any;
  request: any;
  response: any;
}) => {
  const body = await request.body();
  const { isComplete } = await JSON.parse(body.value);

  if (isMissing(isComplete)) {
    response.body = { msg: "isComplete is missing from the request body" };
    response.status = 400;
    return;
  }

  const todo: Todo | undefined = findTodo(params.id);
  const updatedTodo: any = { ...todo, isComplete };
  const newTodos = Todos.map((todo) =>
    todo.id === updatedTodo.id ? updatedTodo : todo
  );

  Todos.splice(0, Todos.length);
  Todos.push(...newTodos);

  response.body = { msg: "Todo updated!", data: updatedTodo };
  response.status = 200;
};

export const deleteTodo = async ({
  params,
  response,
}: {
  params: any;
  response: any;
}) => {
  const newTodos = Todos.filter((todo) => todo.id !== params.id);

  Todos.splice(0, Todos.length);
  Todos.push(...newTodos);

  response.body = { msg: "Todo deleted!", data: newTodos };
  response.status = 200;
};
```

All that we need to do now is use these functions in our `server.ts` file.

`./server.ts`
```javascript
import { Application, Router } from "https://deno.land/x/oak/mod.ts";

import {
  addTodo,
  deleteTodo,
  getTodo,
  getTodos,
  updateTodo,
} from "./src/controllers/todos.ts";

const router = new Router();
const port = 3000;

// Our routes
router
  .get("/todos", getTodos)
  .get("/todos/:id", getTodo)
  .delete("/todos/:id", deleteTodo)
  .patch("/todos/:id", updateTodo)
  .post("/todos", addTodo);

const app = new Application();

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

console.log(`Listening on port ${port}...`);

await app.listen({ port });
```


### And that's it!

We've built this in a way that will allow us to continue to add more routes. We've separated the parts to resemble an MVC approach, minus the view. It's also important to note that we were able to build this without the set up required for running a server with Node and Express. This was also done with no NODE_MODULES!!! I'm excited to see where Deno will be in the next couple of months as support continues to grow. 

For more information and resources I recommend this [post](https://dev.to/aralroca/from-node-to-deno-5gpn#nodemon).

All the source code is located [here](https://github.com/rosborne132/deno_oak_rest_api_example/tree/master)