loading...
Cover image for Create a Simple REST API with Deno

Create a Simple REST API with Deno

r0b profile image Robert Osborne ・4 min read

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.

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.

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.

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.

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.

{
    "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.

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

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

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

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

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.

All the source code is located here

Posted on Apr 30 by:

r0b profile

Robert Osborne

@r0b

Software Engineer in the Portland area.

Discussion

markdown guide