Iniciando con Deno - API Rest
Tags: deno, javascript, typescript
Published: 2020-06-01 02:34:47

La primera versión de Deno fue lanzada el 13 de mayo del 2020. Este Post esta enfocado en ser una guía para aquellos que estamos aprendiendo Deno, para ello realizaremos una API Rest con este interesante Javascript desde el servidor.

# Que es Deno?

Como lo dice en su propio sitio web [deno.land](https://deno.land/), es un simple, moderno y seguro runtime para Javascript y Typescript que usa el motor V8 de Chrome y esta construido con Rust.

# Que vamos a realizar?

Comenzaremos con la instalación de Deno en windows, para luego crear una API Rest con Deno basado en el clásico patrón de diseño MVC (modelo-vista-controlador) con el framework "Oak", la capa de "vista" la dejaremos para otro post donde conectaremos nuestra Api Deno con un front realizado con React JS.

Estructura de la aplicación:

![estructura-api-deno](https://i.ibb.co/PhLRw5N/estructura-api-deno.png)

Contenido:

* Login con auth middleware (JWT)
* CRUD con modelo (interface) Car y User
* Creación de auth middleware
* Configuración de drun para el reinicio automático del servidor

#Instalación

Deno es de simple instalación, solo debemos abrir "PowerShell" y ejecutar el siguiente comando:

```powershell
iwr https://deno.land/x/install/install.ps1 -useb | iex
``` 

Obtendremos el siguiente resultado:

```powershell
Deno was installed successfully to C:\Users\carlo\.deno\bin\deno.exe
Run 'deno --help' to get started
``` 

Luego podemos ejecutar el siguiente comando para ver el "Hola Mundo" que nos provee el mismo Deno:

```powershell
deno run https://deno.land/std/examples/welcome.ts
``` 
El cual nos mostrara lo siguiente:

```powershell
Compile https://deno.land/std/examples/welcome.ts
Welcome to Deno 🦕
``` 

Y listo ya tenemos instalado Deno en nuestro windows, para poder instalar en linux o mac deben realizar algo similar, para ello ejecutar los comandos que nos indica la documentación oficial en el siguiente link: [deno instalación](https://deno.land/#installation).

#Archivo `server.ts` el inicio de nuestra app

Este archivo es que inicializara nuestro servidor web, para ello implementaremos el framework "oak" de deno:

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

//Set Host and Port
const HOST = "127.0.0.1";
const PORT = 4000;

const app = new Application();

// Logger
app.use(async (ctx, next) => {
  await next();
  const rt = ctx.response.headers.get("X-Response-Time");
});

// Timing
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});

console.log(`Welcome API Deno :) ${HOST}:${PORT}...`);
await app.listen(`${HOST}:${PORT}`);


```

Al principio de nuestro código llamamos a Application de oak, esto nos servirá para poder iniciar nuestro servidor deno, luego seteamos el host y el puerto, que en nuestro caso serán localhost y 4000 respectivamente. Después realizamos la configuración de nuestro logger mas el timing de los request que hagamos a nuestra aplicación:

```powershell
POST http://localhost:4000/users/login - 4ms

``` 

También realizamos un console.log de un welcome a la api y iniciamos al final nuestra app, nos debería entregar por consola lo siguiente:

```powershell
Welcome API Deno :) 127.0.0.1:4000...
``` 
# Drun y `config.ts`

Drun es una librería que nos ayuda a reiniciar nuestra aplicación mientras estamos desarrollando, para los que hemos desarrollado con node js, es similar a lo que hace Nodemon.

Para instalar drun es necesario ejecutar el siguiente comando en la consola:

```powershell
deno install --allow-read --allow-run --unstable https://deno.land/x/drun@v1.1.0/drun.ts
```

Luego creamos un archivo en la raiz llamado `drun.json` y colocamos el siguiente código:

```json
{
  "entryPoint": "./server.ts",
  "cwd": "./",
  "excludes": [""],
  "runtimeOptions": ["--allow-net", "--allow-read"]
}

```

Para iniciar nuestro proyecto con drun es necesario ejecutar el comando `drun` y listo, ya podemos trabajar sin preocuparnos de reiniciar el servidor de forma manual.

También crearemos en la raíz de nuestro proyecto, el archivo `config.ts` y colocaremos el siguiente código:

```typescript
export const key = "your-secret-key";
```
Esta sera nuestra "key" para después configurar nuestro JWT.

#Definición de modelos (interfaces o entidades)

Utilizaremos arreglos de objetos para emular nuestra base de datos, para que el post no sea demasiado extenso.

Estos modelos son los que representan a una entidad de la base de datos, estos los vamos a tener dentro de la carpeta "types" de nuestra aplicación:

#### `/types/user.ts`

```typescript
export interface User {
  id: string;
  username: string;
  password: string;
}

``` 

#### `/types/car.ts`

```typescript
export interface Car {
  id: string;
  model: string;
  price: number;
}


```

#Controladores

El controlador es el encargado de interactuar entre nuestra entidad y la vista.

#### `/controllers/car.ts`

```typescript
import { Car } from "../types/car.ts";

//Array cars
let cars: Array<Car> = [
  {
    id: "1",
    model: "Kia Morning",
    price: 5490990,
  },
  {
    id: "2",
    model: "Kia Cerato",
    price: 10990990,
  },
  {
    id: "3",
    model: "Kia Sportage",
    price: 14990990,
  },
  {
    id: "4",
    model: "Kia Stinger",
    price: 29990990,
  },
  {
    id: "5",
    model: "Kia Rio",
    price: 7990990,
  },
];

//Return all cars from databases
const getCars = ({ response }: { response: any }) => {
  response.body = cars;
};

//Return car by id
const getCar = ({
  params,
  response,
}: {
  params: { id: string };
  response: any;
}) => {
  const car = cars.filter((car) => car.id == params.id)[0];
  if (car) {
    response.status = 200;
    response.body = car;
  } else {
    response.status = 404;
    response.body = { message: "404 Not found" };
  }
};

//Creates new car
const createCar = async ({
  request,
  response,
}: {
  request: any;
  response: any;
}) => {
  const body = await request.body();
  const car: Car = body.value;
  cars.push(car);
  response.body = { success: true, data: car };
  response.status = 201;
};

//Update existing car
const updateCar = async ({
  params,
  request,
  response,
}: {
  params: { id: string };
  request: any;
  response: any;
}) => {
  const car = cars.filter((car) => car.id == params.id)[0];
  if (cars) {
    const body = await request.body();
    car.model = body.value.model;
    car.price = body.value.price;
    response.status = 200;
    response.body = {
      success: true,
      data: cars,
    };
  } else {
    response.status = 404;
    response.body = {
      success: false,
      message: "Car not found",
    };
  }
};

//Delete car
const deleteCar = ({
  params,
  response,
}: {
  params: { id: string };
  response: any;
}) => {
  cars = cars.filter((car) => car.id !== params.id);
  response.status = 200;
  response.body = { success: true, message: "Car removed" };
};

export { getCars, getCar, createCar, updateCar, deleteCar };


```

En este controlador, llamamos a nuestra entidad Car para luego crear nuestro array de automóviles. Luego realizamos los siguientes métodos CRUD:

* getCars   -> Traer todos los automóviles
* getCar    -> Traer automóvil por su id
* createCar -> Crear nuevo automóvil
* updateCar -> Actualizar automóvil por su id y parámetros
* deleteCar -> Eliminar automóvil por su id

Es importante mencionar que este controlador esta realizado sin el framework Oak, a diferencia del controlador a continuación, que si utiliza este framework, esto mas que nada para poder ver como seria solo con deno y también con oak.

#### `/controllers/user.ts`

```typescript
import { User } from "../types/user.ts";
import { Context } from "https://deno.land/x/oak/mod.ts";

import {
  makeJwt,
  setExpiration,
  Jose,
  Payload,
} from "https://deno.land/x/djwt/create.ts";

import { key } from "../config.ts";

//Array users
let users: Array<User> = [
  {
    id: "1",
    username: "jdoe@example.com",
    password: "1234",
  },
  {
    id: "2",
    username: "jdae@example.com",
    password: "4321",
  },
];

const header: Jose = {
  alg: "HS256",
  typ: "JWT",
};

//login user
const login = async (ctx: Context) => {
  //get values from body request
  const { value } = await ctx.request.body();
  //iterate array users

  const user = users.filter((x) => x.username === value.username);

  if (user.length != 0) {
    if (
      user[0].username === value.username &&
      user[0].password === value.password
    ) {
      const payload: Payload = {
        iss: user[0].username,
        exp: setExpiration(new Date().getTime() + 50000),
      };

      //create jwt previous condition ok
      const jwt = makeJwt({ key, header, payload });
      if (jwt) {
        // response jwt
        ctx.response.status = 200;
        ctx.response.body = {
          id: user[0].id,
          username: user[0].username,
          jwt,
        };
      } else {
        // if error, response code 500
        ctx.response.status = 500;
        ctx.response.body = {
          message: "Internal error server",
        };
      }
      return;
    } else {
      //credentials wrong
      ctx.response.status = 422;
      ctx.response.body = {
        message: "Invalid username or password",
      };
    }
  } else {
    //user not found in db
    ctx.response.status = 400;
    ctx.response.body = {
      message: "User not found in database",
    };
  }
};

export { login };
```
Iniciamos llamando a la entidad `User` para poder utilizarla, luego importamos los diferentes métodos que necesitamos para la generación del token, desde la librería `djwt`. Importamos la `key` desde el archivo `config.ts` que tenemos en la raíz de nuestro proyecto. Luego procedemos a crear nuestro array de usuarios.

Creamos nuestro método `login`, es importante mencionar que en este controlador hacemos uso de `Context` de Oak para manipular los request y response. Sacamos los values del request y realizamos un `filter` para poder ver si el usuario existe, en el caso que no, enviamos una respuesta 400 indicando en un `message` que el usuario no existe en nuestra base de datos. En caso que si exista, realizamos la comparación de la contraseña, si no coincide, entregamos una respuesta 422 con un mensaje de credenciales erróneas, en caso que si coincide procedemos a crear el token para responder en un objeto, el id, username y el jwt.

#Rutas

Crearemos un archivo router para poder manejar las rutas de nuestra aplicación de manera simple. Para esto creamos la carpeta `router` y dentro el archivo `router.ts`:

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

import {
  getCars,
  getCar,
  createCar,
  updateCar,
  deleteCar,
} from "../controllers/car.ts";

import { login } from "../controllers/user.ts";

import { authMiddleware } from "../middlewares/auth.ts";

const router = new Router();

router
  .post("/users/login", login)
  .get("/cars", authMiddleware, getCars)
  .get("/car/:id", authMiddleware, getCar)
  .post("/cars", authMiddleware, createCar)
  .put("/cars/:id", authMiddleware, updateCar)
  .delete("/cars/:id", authMiddleware, deleteCar);

export default router;

```
Partimos llamando a `Router` desde Oak para poder crear el `router`, luego importamos nuestros métodos CRUD desde el controlador `car` y también método login desde el controlador `user`. En este punto también traemos algo nuevo, nuestro `authMiddleware` para poder trabajar nuestros métodos solicitando JWT, veremos como desarrollarlo en la siguiente sección. Después mapeamos nuestras peticiones: GET, POST, PUT y DELETE, e indicando donde necesitamos que actué nuestro `authMiddleware`, en este caso dejaremos todos los métodos protegidos, a excepción de `/users/login` que sera publico, finalmente exportamos nuestro `router`. Ya con nuestro archivo listo podemos modificar nuestro archivo `server.ts` para agregar las rutas a nuestra aplicación:

`/server.ts`

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

const HOST = "127.0.0.1";
const PORT = 4000;
const app = new Application();

// Logger
app.use(async (ctx, next) => {
  await next();
  const rt = ctx.response.headers.get("X-Response-Time");
  console.log(`${ctx.request.method} ${ctx.request.url} - ${rt}`);
});

// Timing
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});

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

console.log(`Welcome API Deno :) ${HOST}:${PORT}...`);
await app.listen(`${HOST}:${PORT}`);

```
De esta forma integramos las rutas a nuestro archivo `server.ts`.

# Middlewares

Crearemos un middleware el cual nos servirá para proteger nuestras rutas y así puedan acceder a los datos solo los usuarios registrados en nuestra aplicación. Comenzamos creando una carpeta en la raíz `/middlewares` para luego crear el archivo:

`/middlewares/auth.ts`

```typescript
import { Context } from "https://deno.land/x/oak/mod.ts";
import { validateJwt } from "https://deno.land/x/djwt/validate.ts";

import { key } from "../config.ts";

const authMiddleware = async (ctx: Context, next: any) => {
  const headers: Headers = ctx.request.headers;
  const authorization = headers.get("Authorization");
  if (!authorization) {
    ctx.response.status = 401;
    ctx.response.body = { message: "Header : Authorization" };
    return;
  }
  const jwt = authorization.split(" ")[1];
  if (!jwt) {
    ctx.response.status = 401;
    ctx.response.body = { message: "JWT is necessary" };
    return;
  }
  if (await validateJwt(jwt, key, { isThrowing: false })) {
    await next();
    return;
  }

  ctx.response.status = 401;
  ctx.response.body = { message: "Invalid jwt token" };
};

export { authMiddleware };


```

Iniciamos el archivo, trayendo a `Context` desde Oak, para trabajar con request y response. Luego traemos `validateJwt` desde djwt, en este caso `authMiddleware` recibe como parámetro context y next, por convención se utiliza next y básicamente este es el argumento para dar paso a la siguiente función post-middleware. Capturamos `headers` desde el request utilizando context para obtener la cabecera "Authorization", despues validamos si existe esta cabecera, de no ser así, enviamos una respuesta código 401 indicando que es necesaria. Luego utilizamos la función `split()` para poder obtener el token:

*El token desde el cliente y en la cabecera `Authorization` deberia venir con la siguiente configuración `Bearer ${token}`, básicamente split divide esta cadena donde encuentra un espacio, el `[1]` equivale al nuevo array que genera split, quedando en la posición `[0]` `Bearer` y en la posición `[1]` el token que necesitamos.

Después validamos si es que hay token, si no hay, enviamos una respuesta 401 indicando que el JWT es necesario, en caso contrario, validamos el token con nuestra función `validateJwt()` y aplicamos next() para que de esta forma, el flujo pueda seguir hacia la siguiente función:

` .get("/cars", authMiddleware, getCars)` en este ejemplo, para que continué con `getCars` 

De lo contrario, enviamos una respuesta 401 indicando que el JWT es invalido, por que por ejemplo, este ya expiro.

#Conclusión

De esta forma, podemos tener nuestra API Rest con Deno y una estructura muy similar a como se trabajaría en Node JS. Es importante mencionar que Deno utilice nativamente typescript, da una mayor facilidad a la detección de errores, sobre todo de "tipos de datos", agilizando el proceso de desarrollo ya que podemos detectar errores tempranamente. Otra característica interesante es su "Standard Library" que trae por defecto, y la descentralización de las librerías de terceros creando un cache con esta, para no llamarlas en cada momento.

Les dejo el código en el siguiente repositorio de gitlab: [Iniciando con Deno - API Rest](https://gitlab.com/ogmios/deno-api) 

Este es el primer post que hago, cualquier feedback que quieran dejar para mejorar en los siguientes post que vendrán, es totalmente bienvenida.

Los 2 siguientes post en los que estaré trabajando seran:

*Documentación de nuesta API con POSTMAN
*Creación de front-end con React (Hooks) consumiendo nuestra API REST.

Saludos y hasta luego.