Simple Deno API with Oak, deno_mongo and djwt
By: Haris Brackovic
Tags: Nodejs, Deno, JavaScript, Typescript

Simple RestAPI

If you come from the NodeJS world, the first thing you usually do when creating a new app is to run an ‘npm init’ command and follow the process of creating a package.json file.

But with Deno, things are different. There is no package.json, no npm, and no node modules. Just open your favorite IDE (I am using VS Code), navigate to your project folder and create an app.ts file (it will be our project’s main file).

The first thing we want to set up in the app.ts is our http server to be able to handle http requests that we plan to develop later on. For this purpose, we will use Oak as our middleware framework for the http server. Probably you never heard of Oak but don’t worry, it is the same as Koa.js (popular web app framework for NodeJS).

To be more clear, Oak is just a version of Koa.js customized for Deno, so most of the Koa.js documentation also applies to Oak. For module import, Deno uses official ECMAScript module standards, and modules are referenced using a URL or a file path and includes a mandatory file extension.

Our app.ts should like this:

app.ts (initial)

You will probably get an error that TypeScrip does not allow extensions in the module path. To resolve this issue, download and enable Deno’s VS Code extension (enable it only for Deno projects).

In our app.ts we imported Oak’s Application object that contains an array of middleware functions. We will use it to setup the http server, and to later bind a router, global error handler, and auth middleware.

In NodeJS, this Application represents the app we import from the express npm package. The Oak module has been imported from the Deno’s Third-Party Modules. Beside that, we defined an Application instance, host, port, and we set our server to listen to ‘localhost:4000’ address.

As you can notice, Deno allows usage of top-level await, meaning that we do not need to wrap await call inside of the async function on the module level. One big plus for Deno! If you run the app.ts file now, using the command ‘deno run app.ts’ you will get an error:

PermissionDenied error

As mentioned before, Deno does not have permission to access the network so we need to explicitly emphasize it: ‘deno run — allow-net app.ts’. Now, the app will successfully compile and run, and we have our server up and running.

In your browser, navigate to localhost:4000 and you should get “Hello World” displayed.

After the initial server setup, we can move on to creating and configuring MongoDB instance for our service. Before you proceed with the db configuration, please make sure you have MongoDB installed, and local service running.

Deno provides (unstable for now) module for MongoDB and we will get it from the Third Party Module. Since the module is in continuous development, the module versions will be changing fast.

Create a new config folder and add two new files in it: db.ts and config.ts that will be used for the project’s config variables. The db.ts content should now be as follows (code will be explained afterward):

db.ts

We imported the init method and MongoClient from the deno mongo module. The init function is used to initialize the plugin. The other part with the code is pretty straight forward; we have defined DB class with two methods, one for connecting to local db service, other as a getter to return database name. At the end, we created a new instance of our database and export it for usage in other project’s modules.

For now, we will not run our new code, so the deno_mongo module is currently unavailable in our app. With the next execution of ‘deno run’ command, all modules will be downloaded and cached locally so any further code execution will not require any downloads, and the app will compile much faster. This is how Deno handles modules.

Since we finished the database setup, we can move on with the controller methods. We will make this very simple by implementing CRUD operations for the ‘Employee’ model. At the end, we will create an auth middleware to protect create, update, and delete requests. For the project simplicity, we will skip the part of creating ‘singup/signin’ methods but instead make a simple login that will accept any username and password to generate a JWT (json web token). So, let’s go.

Create a new employeeCtrl.ts file. We will first implement a getEmployees method that will return an array of employees with all corresponding properties: id, name, age, and salary. Our controller should look like this:

employeeCtrl.ts (getEmployees method)

Since we are using TypeScript, we imported Context (from the Oak module) to define the type of our response object and later on our request object. We also took our db name and defined ‘employees’ collection. After the interface signature, the get method is implemented. It will fetch all data from the ‘employees’ collection and return data together with the response status. If the collection is empty, a message will be set as a part of the response body. To test our method, we need to define a route.

Create new file called router.ts:

router.ts

Router implementation is the same as the one using express in NodeJS: Import Router from the corresponding (in our case Oak) module and bind the controller method to the defined route. The last thing to do before testing our route and method is to set our app to use the router. Navigate to app.ts file and update its content as follows:

app.ts (router and config.ts added)

To make the code more readable, we will introduce config.ts file from this point to store all configuration/env variables. It should be saved in the config folder:

config.ts

The power of TypeScript and Deno allows us to have access to all types in runtime, and to write strongly typed code.

To run the app execute the following command:

deno run --allow-net --allow-write --allow-read --allow-plugin --allow-env --unstable app.ts

Regarding the permissions we need to run our app, we have set a few of them:

allow-write and allow-read: allow Deno to have access to the disk, in our case to the local database service

allow-plugin: allow Deno to load plugin

allow-env: allow access to the env property

unstable: required since the deno_mongo plugin is in unstable mode currently

If your code compiled successfully and all required modules downloaded, the terminal will show the ‘Listening on port: 4000’ message. If you face any compile errors, do not worry, they are nicely explained and you should be able to resolve the issues easily, especially in this simple scenario.

To test the route and the controller method I will use Postman and the result should be:

Postman (GET /employees)

We can now move on to creating additional controller methods. Navigate back to the employeeCtrl.ts and add POST(addEmployee) and PUT(updateEmployee) actions:

employeeCtrl.ts (addEmployee and updateEmployee added)

These two methods of implementation should be understandable.

To add a new employee, we need to read the values from the request’s body. Check if it contains all necessary properties (usually done by implementing a validation library but Deno currently does not provide any), and save the record in an appropriate collection.

To update an existing employee, read the employee’s id provided through the params, search its properties, and make the updates based on the request’s body.

It is important to mention that we defined our updateEmployee’s input parameters as ‘any’ type. Currently, the Context's request object does not recognize 'params' as its property and the code will not be able to compile. This issue is related to the current version of the Oak module but the documentation states that params, request, and response, are all derived from the Context object so we are good to proceed.

Hopefully, this will be resolved in the near future. To make these actions alive, we need to define their routes. Jump to the router.ts and add these two lines of code:

router.ts (routes for addEmployee and updateEmployee added)

Now, if you rerun the app, you can test the new methods implementation:

Postman (POST /employees)

Postman (PUT /employees/:id)

To finish our CRUD, we need to add two more methods: getEmployeeById and deleteEmployee:

employeeCtrl.ts (getEmployeeById and deleteEmployee added)

This method implementation is very similar to the previous one, except it contains different database actions for deleting and updating records. To wrap up our API, let’s update our router and give a test to the new routes.

router.ts (routes for getEmployeeById and deleteEmployee added)

Postman (GET /employees/:id)

Postman (DELETE /employees/:id)

Now we have a simple API up and running. Comparing it with NodeJS, Deno’s implementation seems very similar. It should only take some time to get familiar with different module imports, TypeScript, and permissions handling, which can be a real bottleneck in the beginning. Also, we need to take into consideration that there will be changes until Deno finds a stable setup.