Building a server with Oak | Deno.
Published: 2023-02-02 17:25:00

---
title: Building a server with Oak | Deno.
published: true
date: 2023-01-19 17:32:52 UTC
tags: 
canonical_url: https://carlosmv.hashnode.dev/building-a-server-with-oak-deno
---

In this article, we are going to build a simple web server using Oak.

The web server will serve files, and perform CRUD operations. And we will use DenoDB as an ORM.

### Requirements

- Deno installed

### What is Oak?

Oak is a middleware framework for Denos native HTTP server, Deno Deploy, and Node.js 16.5 and later. It also includes a middleware route. This middleware framework is inspired by [Koa](https://github.com/koajs/koa).

### Project structure

```
oak-demo/
    file/
        index.html
    controllers/
        Controllers.ts
    model/
        Person.ts
    db.ts
    main.ts

```

### Hello World

main.ts

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

const router = new Router();
router.get("/", (ctx) => {
  ctx.response.body = "Hello World"
});

const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());

app.listen({ port: 8080 });

```

Here, we import Application and Router classes and create a `Router()` instance.

We use the `get()` method and pass to it a path and a handler. The response body is a "Hello World" message.

Then we create an `Application()` instance. And use the `use()` method to register our middleware. Finally, we use the `listen()` method, to start listening for requests.

### Serving Files.

main.ts

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

const ROOT_DIR = "./file";

const app = new Application();

app.use(async (ctx) => {
  await send(ctx, "/index.html", {
    root: ROOT_DIR,
  });
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen({ port: 8080 });

```

In this snippet, we use the `send()` function to serve a file from the local file system. We pass it the file's path and the root directory.

Deno requires read permission for the root directory.

```
Deno run --allow-net --allow-read main.ts

```

### Deno DB

We create a model folder in our root directory and create a file called `Person.ts` .

Person.ts

```
import { DataTypes, Model} from 'https://deno.land/x/denodb@v1.2.0/mod.ts';

export class Person extends Model {
    static table = "person";
    static timestamps = true;

    static fields = {
        id: { primaryKey: true, autoIncrement: true },
        firstName: DataTypes.STRING,
        lastName: DataTypes.STRING,
    };
}

```

In Person.ts we need to import Datatypes and Model classes from DenoDB and then create a Person class, that inherits the properties and methods from the Model class.

We named our table "person" and we mark timestamps as true to show the time when a row is created and updated.

Then, we define the fields of our model and its data types, in this case: `id`, `firstName`, and `lastName`.

After that, we create a file in our root directory where we going to connect the database. I going to use SQLite, but DenoDB has support for PostgreSQL, MySQL, MariaDB, and MongoDB.

db.ts

```
import { Database, SQLite3Connector } from 'https://deno.land/x/denodb@v1.2.0/mod.ts';

import {Person} from './model/Person.ts';

const connector = new SQLite3Connector({
    filepath: "./database.sqlite",
});

export const db = new Database(connector);

db.link([Person])

export default db

```

We need to import Database and SQLite3Connector from Deno Db and Person class from the model module.

We create an immutable variable to store an instance of the SQLite connector and specify the path of the SQL file. Then, we create an immutable variable to store an instance of a database class, connect our database, and after that, we link it to the model.

Now we create a controllers folder in the root directory and create a file to define our controllers.

### Controllers.ts

In this file we define our handlers to perform CRUD operations. We will define 5 handlers, one to perform POST requests, one to perform PUT requests, one to perform DELETE requests, and two to perform GET requests.

POST request

```
import { Context, Status } from "https://deno.land/x/oak@v11.1.0/mod.ts";
import {Person} from '../model/Person.ts';

export const CreatePerson = async (ctx: Context) => {
    const person = await ctx.request.body({type:"json"}).value;

    try {

        const createdPerson = await Person.create(person);

        ctx.response.body = createdPerson;
        ctx.response.type = "json";
        ctx.response.status = Status.Created;

    } catch( _err) {

         ctx.response.status = Status.InternalServerError;
    }
};

```

Here we create a variable to store the request body and pass it as an argument to the create method, to create a record in our database.

If the operation is successful, it will send the status code `201` as a response and `Internal Server Error` if it's not.

GET requests

```
export const AllPersons = async (ctx: Context) => {

    try {
        const person = await Person.all()
        ctx.response.body = person;
        ctx.response.type = "json";
        ctx.response.status = Status.OK;

      } catch(_err) {

        ctx.response.status = Status.InternalServerError;
      }
};

```

In `AllPersons` handlers we retrieve all the records in the database using the `all()` method and store them in the variable `person`. The handler sends all the records and `200` status code as a response.

If there's an error, the handler will send an `Internal Server Error` as a status code.

```
export const GetPerson = async (ctx: Context) => {
    const id = ctx.params.id

    try {
        const person = await Person.where('id', id).first()

        if (!person) {

            ctx.response.status = Status.NotFound

        } else {
            ctx.response.body = person;
            ctx.response.type = "json";
            ctx.response.status = Status.OK;

        }  

    } catch (_err) {

      ctx.response.status = Status.InternalServerError;

    } 
}

```

Here, we extract the parameter `id` from the path and store it in a variable. Then, we pass the `id` as an argument to the `where()` method, to retrieve the record that matches the ID passed and send it as a response with a `200` status code. If there is no record that matches the ID passed, the handler will send a `Not Found` status code.

PUT requests

```
export const UpdatePerson = async (ctx: Context) => {
  const id = ctx.params.id
  const reqBody = await ctx.request.body().value;

  try {
      const person = await Person.where('id', id).first()

      if (!person) {
      ctx.response.status = Status.NotFound

      } else {

        await Person.where('id', id).update(reqBody)
        ctx.response.body = "Person Updated";
        ctx.response.type = "json";
        ctx.response.status = Status.OK;

      }

    } catch (_err) {
        ctx.response.status = Status.InternalServerError;

  }     

};

```

In `UpdatePerson` handler, we extract the `id` from the path and the request body. Then, we use the `where()` method to look for a record that matches the ID. If the operation is successful, we pass the request body as an argument to the `update()` method. After the record is modified, the handler sends "Person Updated" as a response body and `200` as a status code.

DELETE requests

```
export const DeletePerson = async (ctx: Context) => {
    const id = ctx.params.id

    try {
        const person = await Person.where('id', id).first()

        if (!person) {

            ctx.response.status = Status.NotFound

        } else {
            Person.delete()
            ctx.response.body = "Person deleted";
            ctx.response.type = "json";
            ctx.response.status = Status.OK;

        }     
    } catch (_err) {

      ctx.response.status = Status.InternalServerError;

    }        

};

```

Here we extract the `id` from the path and retrieve a record that matches the ID, then we use the `delete()` method to delete the record. After the record is deleted, the handler sends "Person deleted" as a response body, and `200` as a status code.

### main.ts

```
import { Application,Router, send } from "https://deno.land/x/oak@v11.1.0/mod.ts";
import {db } from "./db.ts"
import {AllPersons, GetPerson, CreatePerson, UpdatePerson, DeletePerson} from "./controllers/Controllers.ts"

const ROOT_DIR = "./file";

const app = new Application();

const router = new Router();

router.get("/persons", AllPersons)
router.get("/person/:id", GetPerson);
router.post("/person", CreatePerson);
router.put("/person/:id", UpdatePerson);
router.delete("/person/:id", DeletePerson);

app.use(router.routes());

app.use(async (ctx) => {

  await send(ctx, "/index.html", {
    root: ROOT_DIR,
  });
});

await db.sync()

app.listen({ port: 8080 });

```

In `main.ts` we import all our handlers from `Controller.ts`. Then we create a router instance and define our routes. For every HTTP method, we define a path and pass the handler it will call.

We use the `use()` method to add our routes as middleware.

We use `sync()` method to start Deno DB.

To run the server we write on our command line:

```
deno run --allow-all main.ts

```

### Adding Logging

```
...
import logger from "https://deno.land/x/oak_logger/mod.ts";

...

app.use(logger.logger);
app.use(router.routes());
...

app.listen({ port: 8080 });

```

To add logging to our server, we import the module `oak_logger` . And add `logger.logger` as middleware on top of routes. Now, every time a request is made to our server, it will show the loggings in our command line. If we add the logger after `app.use(router.routes());` , it will not show the loggings of the routes.

### Conclusion

I found Oak easy to use, and its documentation helps a lot, it is well-written. I find it very similar to Gin even when they are written in different languages. Also, you can visit the [awesome-oak](https://oakserver.github.io/awesome-oak/) page, it has a list of community projects for Oak.

Thank you for taking the time to read this article.

If you have any recommendations about other packages, architectures, how to improve my code, my English, or anything; please leave a comment or contact me through [**Twitter**](https://twitter.com/Carlos_marcv), or [**LinkedIn**](https://www.linkedin.com/in/carlos-marcano-a2135a134/).

The source code is [here](https://github.com/carlosm27/oak-demo).

### Reference

[Oak Documentation.](https://deno.land/x/oak@v11.1.0/mod.ts)

[Oak's GitHub page.](https://github.com/oakserver/oak)

[Static file in Deno Oak.](https://medium.com/deno-the-complete-reference/static-file-server-in-deno-oak-241c69aac7e3)

[Awesome-oak](https://oakserver.github.io/awesome-oak/).