Creating a simple REST API in Deno
Tags: deno, security, engineering, microservices
Published: 2022-02-22 11:33:15

Written by Eazie Enibe
_Originally published on January 28th 2021_

Building a simple REST Api with a modern and secure runtime for Typescript (Deno).

If you are a backend-for-frontend enthusiast looking for an alternative to NodeJS then you should definitely try out DenoJS. Also created by Ryan Dahl of Node — it comes with some great features such as out-of-the-box Typescript support, etc., which makes it a worthwhile consideration for your next project. In this tutorial, we will not cover in-depth introductory topics on Deno, for that, you can visit the official [Deno](https://deno.land/) site.

This tutorial is a beginner's guide to REST APIs with DenoJS. We will be building a simple boilerplate that can be used as a basic blueprint for any of your applications.

## Getting Started

First you will need to install Deno. You can find the instructions [here.](https://deno.land/#installation)

For this tutorial we will be using [Oak.](https://deno.land/x/oak@v6.3.2) It is a popular middleware for Deno and I personally find it easier to use in comparison to the others out there such as *deno-express, pogo,* etc.

For the sake of simplicity, our server will be storing an in-memory list of advertisements, their types, and channels.
We will be:

- Creating an advertisement

- Updating an advertisement

- Deleting an advertisement

- Publishing an advertisement

Create a new project directory called __`advertisement-publishing-service`__ and add 3 files called __`server.ts`__, __`routes.ts`__, and __`deps.ts`__ in it. We will be managing our packages in the __`deps.ts`__ file.

We will start by importing the *Application* and *Router* object from Oak.

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

We will then import the *Application* object from __`deps.ts`__ in the __`server.ts`__ and *router* from __`routes.ts`__.

```typescript
import { Application } from "./deps.ts";
import router from "./routes.ts";
```

Next, we derive the __`environment`__, __`host`__ and __`port`__ from Application object.

```typescript
const env = Deno.env.toObject()
const PORT = env.PORT || 3000;
const HOST = env.HOST || 'localhost';
```

```typescript

import { Router } from "./deps.ts";

const router = new Router();

router.get("/api/v1/hello", (context) => {
  context.response.body = {
    success: true,
    msg: "Hello World",
  };
});

export default router;
```

Back in the __`server.ts`__, we will now instantiate the *Application* object and wire up our first route.

```typescript
const app = new Application();
app.use(errorHandler);
app.use(router.routes());
app.use(router.allowedMethods());
app.use(_404);

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

app.listen(`${HOST}:${PORT}`);
```

Now we run our code as shown below.

__`deno run --allow-net --allow-env server.ts`__

Notice that Deno will first download all required dependencies and then listens on port 3000. When you go to 

__`http://localhost:3000/api/v1/hello`__ 

you should see the response below:

__`{
   "success": true,
   "msg": "Hello World"
}`__

## Let's start building

In the project directory, we will now create a new directory called interfaces, in it, we will be exporting interfaces for __`Advertisement`__, __`Channel`__, and __`Type`__.

```typescript
export interface IAdvertisement {
  id: string;
  name?: string;
  description?: string;
  startDate?: string;
  endDate?: string;
  isActive?: boolean;
  type?: Array<IType>;
  channel?: Array<IChannel>;
}

export interface IChannel {
  id: string;
  name: string;
}

export interface IType {
  id: string;
  name: string;
}
```

You could always put the __`Channel`__ and __`Type`__ interfaces in their own files.

We will now create two additional directories __`models`__ and __`services`__ in the model directory. We will also add a file called __`advertisement-model.ts`__ and in the services directory, we will add a file __`advertisement-service.ts`__.

The __`Advertisement`__ class will implement the __`IAdvertisement`__ interface to ensure it is always type-checked when used.

```typescript
    class Advertisement implements IAdvertisement {
      id: string;
      name: string;
      description: string;
      startDate: string;
      endDate: string;
      isActive: boolean;
      type: Array<IType>;
      channel: Array<IChannel>;

      constructor({id, name, description, startDate, endDate, isActive, type, channel}: {
                id: string,
                name: string,
                description: string,
                startDate: string,
                endDate: string,
                isActive: boolean,
                type: Array<IType>,
                channel: Array<IChannel>
            }
      ) {
this.id = id;
this.name = name;
this.description = description;
this.startDate = startDate;
this.endDate = endDate;
this.isActive = isActive;
this.type = type;
this.channel = channel;
      }
  ...
}
```      

Also, we will add a static function that will accept a JSON object or string and convert it to an Advertisement type.

```typescript
static fromJSON(json: IAdvertisement | string): Advertisement {
   if (typeof json === "string") {
            return JSON.parse(json, Advertisement.reviver);
   }
   let advertisement = Object.create(Advertisement.prototype);
   return Object.assign(advertisement, json);
}
```

## Service Layer

In the Advertisement Service class, we will implement the logic that will be used in our controller. We will first load the data to be used in memory, as mentioned earlier, and it will be stored in memory.

```typescript
  loadData = () => {
    const advertiseJSON = readJSON("./data/advertisements.json");
    const adverts = Advertisement.fromJSON(advertiseJSON);
    this.advertisements = Object.values(adverts);
    this.channels = readJSON("./data/channels.json");
    this.types = readJSON("./data/types.json");
  };
```

Here, we implement a function to retrieve a single advertisement by id.

```typescript
fetchAdvertisement = (id: string) =>
  this.advertisements.find(((advertisement) => advertisement.id === id));
```

And next, we create a new advertisement.

```typescript
createAdvertisement = (advertisement: IAdvertisement) => {
  const newAdvertisement = Object.values(advertisement);
  const [first] = newAdvertisement;
  this.advertisements.push(first);

                          ...
};
  ```

###   Update existing advertisements.

  ```typescript
updateAdvertisement = (advertisement: IAdvertisement, id: string) => {
    const updatedAdvertisement: {
      name?: string;
      description?: string;
      startDate?: string;
      endDate?: string;
      type?: Array<IType>;
      channel?: Array<IChannel>;
    } = advertisement;
    this.advertisements = this.advertisements.map((advert) =>
      advert.id === id ? { ...advert, ...updatedAdvertisement } : advert
    );

    return true;
  };
```

## Controller Layer

We will now create a new directory called controller, and in it, we will add a new file called __`advertisement-controller.ts`__. In this class, we will implement all the endpoints that will be defined in our routes class.

Each controller operation must be async and will receive either one or both __`request`__ and __`response`__ objects as parameters. Regardless of the logic that we implement in the end, we must return a response body.

Below is the controller function to return all advertisements.

```typescript
export const getAdvertisements = ({ response }: { response: any }) => {
  response.body = {
    data: AdvertisementService.fetchAdvertisements(),
  };
};
```

Here we make a call to the __`fetchAdvertisements function`__ of the __`AdvertisementService`__ to return a list of all advertisements.

![Creating a rest api with deno blog post, fetch advertisements](//images.ctfassets.net/hqu2g0tau160/519P4MPJWtFfqDAJs7u0p9/3d13a3436f29bf5a93b57e47df791be9/rest_api_image_1.png)

Next, we obtain a single Advertisement.

```typescript
export const getAdvertisement =  (
  { params, response }: { params: { id: string }; response: any },
) => {
  const advertisement = AdvertisementService.fetchAdvertisement(
    params.id,
  );

  if (advertisement === null) {
    response.status = 400;
    response.body = { msg: `Advertisement with id: ${params.id} not found` };
    return;
  }

  response.status = 200;
  response.body = { data: advertisement };
};
```

![Creating a simple rest api with deno blog post, single advertisement](//images.ctfassets.net/hqu2g0tau160/3BxZOluedbX6pEPi3MX5kd/4122d6138e1b4220c2d429dd0856b5fa/creating_a_simple_rest_api_with_deno_image_2.png)

In this case, we pass the id from Params to __`fetchAdvertisement`__ of the __`AdvertisementService`__ class to return a single advert.

### Add an advertisement below.

```typescript
export const addAdvertisement = async (
  { request, response }: { request: any; response: any },
) => {
  if (!request.body()) {
    response.status = 400;
    response.body = {
      success: false,
      msg: "The request must have a body",
    };
    return;
  }

  const data = await request.body().value;

  const advertisement = AdvertisementService.createAdvertisement(
    data,
  );
  response.status = 200;
  response.body = {
    success: true,
    data: advertisement,
  };
};
```

![creating a simple rest api with deno blog post, image update advertisement](//images.ctfassets.net/hqu2g0tau160/23rvkKib8eMp6w6TcoXkNY/618e6d99780be240ab4b2873983e3122/creating_a_simple_rest_api_with_deno_image_3_update_advertisement.png)

### Update Advertisement.

```typescript
export const updateAdvertisement = async (
  { params, request, response }: {
    params: { id: string };
    request: any;
    response: any;
  },
) => {
  const advertisement = AdvertisementService.fetchAdvertisement(
    params.id,
  );

  if (!advertisement) {
    response.status = 404;
    response.body = {
      success: false,
      msg: `Advertisement with id: ${params.id} not found`,
    };
    return;
  }

  const data = await request.body().value;
  const updatedAdvertisement = AdvertisementService.updateAdvertisement(
      data,
      params.id,
    );

  if (updatedAdvertisement) {
    response.status = 200;
    response.body = {
      success: true,
      msg: `Update for advert with id ${params.id} was successful`,
    };
    return;
  }

  response.status = 500;
  response.body = {
    success: true,
    msg: `Update for advertisement with id ${params.id} failed`,
  };
};
```

![Creating a simple rest api in deno, Image delete advertisement ](//images.ctfassets.net/hqu2g0tau160/4pUGRZIpBpEdejqoZM5tAv/eeb037fdc5974e7d9587706e8bc64533/Creating_a_simple_rest_api__image_4.png)

### Delete Advertisement.

```typescript
export const deleteAdvertisement = (
  { params, response }: { params: { id: string }; response: any },
) => {
  const advertisement = AdvertisementService.deleteAdvertisement(
    params.id,
  );
  response.body = {
    success: true,
    msg: "Advertisement removed",
    data: advertisement,
  };
};
```

These could feel a bit repetitive, but you could split each of these operations to separate files to keep it clean, that is, if you are ok with having multiple controller files. In the case of this demo, a single file was sufficient.

![Creating a simple rest api in deno blog post, image update ](//images.ctfassets.net/hqu2g0tau160/3yl1nGbWWmprOb1g5Cu57V/e5727fa4d4e1a445019c10ae88aa3097/Creating_a_simple_rest_api_with_deno_image_5.png)

Next, we will now update our __`routes.ts`__ to define the updated endpoints from the controller.

```typescript
import { Router } from "./deps.ts";
import {
  addAdvertisement,
  deleteAdvertisement,
  getAdvertisement,
  getAdvertisements,
  publishAdvertisement,
  updateAdvertisement,
} from "./controllers/advertisement-controller.ts";

const router = new Router();

router.get("/api/v1/advertisements", getAdvertisements)
  .get("/api/v1/advertisements/:id", getAdvertisement)
  .post("/api/v1/advertisements", addAdvertisement)
  .put("/api/v1/advertisements/:id", updateAdvertisement)
  .put("/api/v1/advertisements/publish", publishAdvertisement)
  .delete("/api/v1/advertisements/:id", deleteAdvertisement);

export default router;
```

## Middlewares

To handle __`404`__ and other HTTP errors, we will add two middlewares. First, we will create a new directory in the root called __`middleware`__ and in it, we will add two files called __`FourZeroFour.ts`__ and __`error-handler.ts`__ .

```typescript
import { Context } from "../deps.ts";

const errorHandler = async (ctx: Context, next: any) => {
  try {
    await next();
  } catch (err) {
    ctx.response.status = 500;
    ctx.response.body = { msg: err.message };
  }
};

export default errorHandler;
```

```typescript
import { Context } from "../deps.ts";

const fourZeroFour = async (ctx: Context) => {
  ctx.response.status = 404;
  ctx.response.body = { msg: "Not Found !!" };
};

export default fourZeroFour;
```

Finally, we can try it out again.

We will run the Deno project in your terminal in the root folder, and 
issue the following command as we did earlier above:

__`deno run --allow-net --allow-env server.ts`__

Deno works with secure resources, which means that we must explicitly request that http calls and access to environment variables must be allowed. The __`--allow-net`__ and __`--allow-env`__ __`flags`__ do the job, respectively.

## Summary

When compared with Node, a few differences can be noted from the project presented above:

- We introduced a file __`dep.ts`__ to manage the URLs for our dependencies because modules/dependencies are loaded remotely and cached locally, while with Node we would use a node package manager that introduces a __`node_modules`__ directory for the same purpose.

- We were able to use Typescript out of the box without any extra configurations as would have been the case with Node.

- We used __`promises`__ extensibly because they are supported out of the box for __`async programming`__ by Deno. In Node __`callbacks`__ are supported by default and promises with additional modules and configurations.

- As seen above, we require specific permissions to access various system resources, e.g. network, env, files, etc. in Deno. The same does not apply for Node, full access is available by default.

- Most importantly, we have out of the box support for __`ES modules`__ and therefore didn’t have to worry about the tediousness of setting up __`Gulp`__ or __`Webpack`__ in our project for it.

These differences to me give Deno a bit of an edge over Node because I didn’t have to spend so much time on the overall project wiring and setup. This was done rather quickly, which allowed me to dive into the actual coding sooner.

That’s all! Now we have a working Deno API with each of the four major CRUD operations. The final code for this tutorial can be found [here](https://github.com/ieazie/advertisement-publisher-service) with some slight differences.

Thanks for reading.

___
Header Image Credits: Jon Tyson by [Unsplash](https://unsplash.com/photos/49ybMyLB7HM)