Build RESTful API Server with Deno and Docker
Tags: deno, webdev, typescript, docker
Published: 2020-05-30 18:26:21

---
title: Build RESTful API Server with Deno and Docker
published: true
description: Deno 1.0 has been released in May, 2020. Deno is a modern runtime that uses V8 and is built in Rust. Compared to its predecessor Node.js, Deno ships with a set of standard modules and secure by default. In this article, we are going to build a RESTful API server with Deno, and deploy it with Docker.
tags: Deno,Web Development,TypeScript,Docker
canonical_url: https://www.345tool.com/blog/build-restful-api-server-with-deno-and-docker
---

> This article originally posted on [345Tool Blog](https://www.345tool.com/blog/build-restful-api-server-with-deno-and-docker)

Deno 1.0 has been released in May, 2020. Deno is a modern runtime that uses V8 and is built in Rust. Compared to its predecessor Node.js, Deno ships with a set of standard modules and secure by default. Although there is still a long way to go, Deno gives the TypeScript users a solid option to build modern web applications. In this article, we are going to build a simple RESTful API server with Deno, and containerize it with Docker.

TLDR: If you prefer reading the code, you can find the working code [here](https://github.com/345Tool/blog-samples/tree/master/build-restful-api-server-with-deno-and-docker) and the version of Deno is 1.2

## What we are going to build
We are going to build a very typical RESTful API server, it will do CRUD operations on backend data based on the requests received. The server contains following logic:

**User Service** - a service to manipulate users data. For demonstrate purposes, the service will only manipulate the data stored in memory.

**API Server** - a Deno http server listens. This is the main part we going to build. When the server started, it exposes two API endpoints GET /users and POST /user.

**Docker Image** - finally we are going to containerize deno runtime and  our application using docker.

## User service
To keep it simple, we use a list of users to mock the database data, and the `addUser` method will insert a new user into the list, the `listUsers` method will return the users list. We will call these methods in server request handlers.

```typescript
export interface User {
  id: number;  name: string;
}

const users: User[] = [
  { id: 1, name: "John" },
  { id: 2, name: "Emily" },
  { id: 3, name: "Kevin" },
];

export const listUsers = (): User[] => {
  return users;
};

export const addUser = (user: User): void => {
  users.push(user);
};
```

## Deno Server
To create a http server in Deno, we are going to use the standard `http` module
```typescript
// index.ts
import { serve, Server } from "https://deno.land/std/http/server.ts";

const app: Server = serve({ port: 4040 });
console.log('server started on localhost:4040')
```
Deno allows us import modules directly from remote url, when the code been executed at first time, Deno will download those remote modules and cache them in local, so next time we can run the code without internet access. Now let's run the code by executing
```bash
deno --allow-net index.ts
```
Notice `--allow-net` flag, it will enable the network access for Deno runtime, otherwise it will be failed to initiate the server. After running, we can see the ouput
```bash
Download https://deno.land/std/http/server.ts
server started on localhost:4040
```
So far, we have create a very basic http server without any functions, next we will make the server to listen for http requests and make responds. Deno implements the http server in a very elegant way, the `Server` class implements `AsyncIterable<ServerRequest>` interface, which allows us to use the `for await` syntax to iterate over the http requests asynchronously. You can read more about `AsyncIterable` in this [awesome article](https://2ality.com/2016/10/asynchronous-iteration.html). Lets add following lines
```typescript
import { serve, Server } from "https://deno.land/std/http/server.ts";

const app: Server = serve({ port: 4040 });

console.log('server started on localhost:4040');

for await (const req of app) {
  req.respond({
    status: 200,
    body: "Hello World!",
  });
}
```
Now we have a http server which will respond `Hello World!` for each incoming request! Next, we are going to define the Restful APIs, we will handle following requests:

- `POST /user` add a new user by calling `addUser` in API handler
- `GET /users` respond with a list of users
- For other requests, we will respond 404 error

Let's add more logic in `for await` block

```typescript
import { serve, Server, ServerRequest } from "https://deno.land/std/http/server.ts";
import { listUsers, addUser, User } from "./user-service.ts";
import { respondNotFound, respondWithBody, parseRequestBody } from "./utilities.ts";

const app: Server = serve({ port: 4040 });

console.log('server started on localhost:4040')

for await (const req of app) {
  switch (req.url) {
    case "/user": {
      switch (req.method) {
        case "POST": {
          const newUser = await parseRequestBody<User>(req);
          addUser(newUser);
          respondWithBody(req, true);
          break;
        }
        default:
          respondNotFound(req);
      }
      break;
    }
    case "/users": {
      switch (req.method) {
        case "GET": {
          respondWithBody<User[]>(req, listUsers());
          break;
        }
        default:
          respondNotFound(req);
      }
      break;
    }
    default:
      respondNotFound(req);
  }
}
```
When a request coming, we use `switch` to check the `req.url` and `req.method` properties, and we only handle the request which both url and method matched our API defines, otherwise we call `respondNotFound` to throw 404 error. As you can see, it is a pretty basic way to triage incoming requests, as the number of APIs growing, for separation of concerns, we can split the code in `switch` block by implementing some middlewares.

Finally, let's take a look at the request handlers, `parseRequestBody` will read the request body binary and convert it to json object.

```typescript
const parseRequestBody = async <T>(req: ServerRequest): Promise<T> => {
  const buf = new Uint8Array(req.contentLength || 0);
  let bufSlice = buf;
  let totRead = 0;
  while (true) {
    const nread = await req.body.read(bufSlice);
    if (nread === null) break;
    totRead += nread;
    if (totRead >= req.contentLength!) break;
    bufSlice = bufSlice.subarray(nread);
  }
  const str = new TextDecoder("utf-8").decode(bufSlice);
  return JSON.parse(str) as T;
};
```

`respondWithBody` and `respondNotFound` are two helper methods to send API respond

```typescript
const respondNotFound = (req: ServerRequest) =>
  req.respond({
    status: 404,
    body: "request not found!",
  });

const respondWithBody = <T>(req: ServerRequest, body?: T) => {
  const headers = new Headers();
  headers.append("Content-Type", "application/json");

  req.respond({
    status: 200,
    headers,
    body: JSON.stringify(body),
  });
};
```

## Docker image
To run our app in docker, first install Deno runtime and setup the path. Finally make sure the app running with `--allow-net` and `--allow-read` flags to have correct permissions

```bash
# Dockerfile
FROM ubuntu:18.04

COPY . server/

RUN apt-get update && apt-get install -y sudo; \
    sudo apt-get update && sudo apt-get -y install curl unzip; \
    curl -fsSL https://deno.land/x/install/install.sh | sh;

WORKDIR server/

EXPOSE 4040

ENV DENO_INSTALL="/root/.deno"

ENV PATH="$DENO_INSTALL/bin:$PATH"

CMD deno run --allow-net --allow-read index.ts
```