Create a server with deno and mongo.
Tags: javascript, deno, typescript
Published: 2020-05-02 21:32:16

Despite the success of Node.js and the great effort done by the community since its creation, Ryan Dahl the creator of the famous javascript runtime, decided however in 2018 to design a new secure runtime for javascript built on V8, Rust, Typescript and Tokio (event loop). He declared that there are some design mistakes in Node.js and he regrets about them, then he has created deno which takes into consideration those anomalies. 
If you are curious about the issue, you can check his [**presentation** ](https://www.youtube.com/watch?v=M3BM9TB-8yA) in JSConf EU conference in 2018.

The purpose of this article is:

- To create an api to manage employees.
- To create environment variables using [**denv**](https://deno.land/x/denv).
- To implement controllers for the api.
- To communicate with a database (mongodb) using [**deno_mongo**](https://deno.land/x/mongo/).
- To use a simple deno framework called [**abc**](https://deno.land/x/abc/).


First of all, you need to install **deno** in your machine, and according to your OS you can choose the suitable command line, check [install section](https://github.com/denoland/deno_install) for more information.

**PS:** At the moment of writing this article, we use **deno v1.0.3** to create our api.


**Let's launch work**

In order to facilitate the design of our server, we need a framework (let's say the equivalent of [express](https://expressjs.com/) in Node.js). In our case, we choose [abc](https://deno.land/x/abc) a simple deno framework to create web applications (abc is not the only framework there are others like [alosaur](https://deno.land/x/alosaur/), [espresso](https://deno.land/x/espresso), [fen](https://deno.land/x/fen/), [oak](https://deno.land/x/oak/), etc...).

First of all, we start by decalring our environment variables in `.env` file:
```
DB_NAME=deno_demo
DB_HOST_URL=mongodb://localhost:27017

```
Then, we create an error middleware to handle errors catched in the controllers:
```ts
import { MiddlewareFunc } from "https://deno.land/x/abc@v1/mod.ts";
export class ErrorHandler extends Error {
  status: number;
  constructor(message: string, status: number) {
    super(message);
    this.status = status;
  }
}
export const ErrorMiddleware: MiddlewareFunc = (next) =>
  async (c) => {
    try {
      await next(c);
    } catch (err) {
      const error = err as ErrorHandler;
      c.response.status = error.status || 500;
      c.response.body = error.message;
    }
  };


```
Then, we create `server.ts` (the main file of our server):

```ts
import { Application } from "https://deno.land/x/abc@v1/mod.ts";
import "https://deno.land/x/denv/mod.ts";
import {
  fetchAllEmployees,
  createEmployee,
  fetchOneEmployee,
  updateEmployee,
  deleteEmployee,
} from "./controllers/employees.ts";
import { ErrorMiddleware } from "./utils/middlewares.ts";

const app = new Application();

app.use(ErrorMiddleware);

app.get("/employees", fetchAllEmployees)
  .post("/employees", createEmployee)
  .get("/employees/:id", fetchOneEmployee)
  .put("/employees/:id", updateEmployee)
  .delete("/employees/:id", deleteEmployee)
  .start({ port: 5000 });

console.log(`server listening on http://localhost:5000`);
``` 
In the first line, you will find that we import modules directly from the internet using an url. The first time you need to import a module, deno fetches it then download cache the dependencies.

The second line calls denv in order to load the enviornment variables from the `.env` file.

The rest of code is almost similar to express, nothing special.

Now, we need to configure our database to interact with the server. Fortunately, there is [deno_mongo](https://deno.land/x/mongo/) a MongoDB database driver developed for deno. It is under construction and still does not contain the different methods of mongodb driver but it is ok for a simple demo.

```ts
import { init, MongoClient } from "https://deno.land/x/mongo@v0.8.0/mod.ts";

class DB {
  public client: MongoClient;
  constructor(public dbName: string, public url: string) {
    this.dbName = dbName;
    this.url = url;
    this.client = {} as MongoClient;
  }
  connect() {
    const client = new MongoClient();
    client.connectWithUri(this.url);
    this.client = client;
  }
  get getDatabase() {
    return this.client.database(this.dbName);
  }
}

const dbName = Deno.env.get("DB_NAME") || "deno_demo";
const dbHostUrl = Deno.env.get("DB_HOST_URL") || "mongodb://localhost:27017";
const db = new DB(dbName, dbHostUrl);
db.connect();

export default db;
```
We create a DB class which allows to start a database connection, so we create a new instance with `DB_NAME` and `DB_HOST_URL`.

Then, we write our controllers, let's start by `createEmployee`:

```ts
import { HandlerFunc, Context } from "https://deno.land/x/abc@v1/mod.ts";
import db from '../config/db.ts';

const database = db.getDatabase;
const employees = database.collection('employees');

interface Employee {
  _id: {
    $oid: string;
  };
  name: string;
  age: number;
  salary: number;
}

interface NewEmployee {
  name: string;
  age: number;
  salary: number;
}

export const createEmployee: HandlerFunc = async (c: Context) => {
  try {
    if (c.request.headers.get("content-type") !== "application/json") {
      throw new ErrorHandler("Invalid body", 422);
    }
    const body = await (c.body<NewEmployee>());
    if (!Object.keys(body).length) {
      throw new ErrorHandler("Request body can not be empty!", 400);
    }
    const { name, salary, age } = body;

    const insertedEmployee = await employees.insertOne({
      name,
      age,
      salary,
    });

    return c.json(insertedEmployee, 201);
  } catch (error) {
    throw new ErrorHandler(error.message, error.status || 500);
  }
};
```
The mongo driver returns an object containing only the `$oid` attribute (I hope it will be updated in the next versions of the module).

To fetch all the employees, we call:

```ts
export const fetchAllEmployees: HandlerFunc = async (c: Context) => {
  try {
    const fetchedEmployees: Employee[] = await employees.find({name:{$ne: null}});

    if (fetchedEmployees) {
      const list = fetchedEmployees.length
        ? fetchedEmployees.map((employee) => {
          const { _id: { $oid }, name, age, salary } = employee;
          return { id: $oid, name, age, salary };
        })
        : [];
      return c.json(list, 200);
    }
  } catch (error) {
    throw new ErrorHandler(error.message, error.status || 500);
  }
};
```

For fetching a given employee by id, `fetchOneEmployee` will be called:

```ts
export const fetchOneEmployee: HandlerFunc = async (c: Context) => {
  try {
    const { id } = c.params as { id: string };

    const fetchedEmployee = await employees.findOne({ _id: { "$oid": id } });

    if (fetchedEmployee) {
      const { _id: { $oid }, name, age, salary } = fetchedEmployee;
      return c.json({ id: $oid, name, age, salary }, 200);
    }

    throw new ErrorHandler("Employee not found", 404);
  } catch (error) {
    throw new ErrorHandler(error.message, error.status || 500);
  }
};
```
Update a given employee:
``` ts
export const updateEmployee: HandlerFunc = async (c: Context) => {
  try {
    const { id } = c.params as { id: string };
    if (c.request.headers.get("content-type") !== "application/json") {
      throw new ErrorHandler("Invalid body", 422);
    }

    const body = await (c.body()) as {
      name?: string;
      salary: string;
      age?: string;
    };

    if (!Object.keys(body).length) {
      throw new ErrorHandler("Request body can not be empty!", 400);
    }

    const fetchedEmployee = await employees.findOne({ _id: { "$oid": id } });

    if (fetchedEmployee) {
      const { matchedCount } = await employees.updateOne(
        { _id: { "$oid": id } },
        { $set: body },
      );
      if (matchedCount) {
        return c.string("Employee updated successfully!", 204);
      }
      return c.string("Unable to update employee");
    }
    throw new ErrorHandler("Employee not found", 404);
  } catch (error) {
    throw new ErrorHandler(error.message, error.status || 500);
  }
};
```
The driver here returns an object containing:
- matchedCount
- modifiedCount
- upsertedId

Finally, to delete an employe: 
```ts
export const deleteEmployee: HandlerFunc = async (c: Context) => {
  try {
    const { id } = c.params as { id: string };

    const fetchedEmployee = await employees.findOne({ _id: { "$oid": id } });

    if (fetchedEmployee) {
      const deleteCount = await employees.deleteOne({ _id: { "$oid": id } });
      if (deleteCount) {
        return c.string("Employee deleted successfully!", 204);
      }
      throw new ErrorHandler("Unable to delete employee", 400);
    }

    throw new ErrorHandler("Employee not found", 404);
  } catch (error) {
    throw new ErrorHandler(error.message, error.status || 500);
  }
};
```
Let's start our server now:

```
deno run --allow-write --allow-read --allow-plugin --allow-net --allow-env --unstable ./server.ts

```
In order, to guarantee a secure exection of the program, deno blocks every access to disk, networks or environment variables. Therefore, to allow the server to be executed, you need to add the following flags: 

- --allow-write 
- --allow-read 
- --allow-plugin 
- --allow-net
- --allow-env

Probably, you will ask yourself " how will I know which flags I have to add to execute the server?". Don't worry you will get a messge in the console log asking you to add a given flag.

Now, you will see something similar to this in your terminal:
```
INFO load deno plugin "deno_mongo" from local "~/.deno_plugins/deno_mongo_40ee79e739a57022e3984775fe5fd0ff.dll"
server listening on http://localhost:5000

```

## Summary

In this article, we :

- Created an employees' api using Deno.
- Created a connection to a mongodb database using mongo driver for deno.
- Used the **abc** framework to create our server.
- Declared the environment variables using **denv**.


You probably realized that we :

- Don't need to initialize a package.json file or install modules under node_modules.
- Import modules directly using urls.
- Add flags to secure the execution of the program.
- Don't install typescript locally because it's compiled in Deno.

That's it folks, don't hesitate to leave a comment if there is any mistake, if you have a question or a suggestion. If you like the article, don't forget to click the heart button or tweet it ;).

## Code
**You            can           find         the       code          here: https://github.com/slim-hmidi/deno-employees-api**


## References
* https://deno.land/
* https://deno.land/v1
* https://deno.land/x/abc
* https://deno.land/x/mongo
* https://deno.land/x/denv
* https://blog.logrocket.com/deno-1-0-what-you-need-to-know/