DEV Community

Cover image for Create an Angular Rest API Mock with Deno
Francesco Leardini
Francesco Leardini

Posted on • Updated on

Create an Angular Rest API Mock with Deno

In one of our Angular courses we are currently using a Node Express server to provide mock data to an Angular application.

As Deno has been released with the official version 1.0 (mid May 2020), I decided to experiment it and write a new web server for the course. The final layout looks like the screenshot below (as you can see nothing fancy from the layout perspective):

Alt Text

This article is a step-by-step, practical guide focused on creating a Rest API, with full CRUD actions, for an Angular application. I will not cover too many details about Deno though, as it would make the post way too long, and there are already plenty of good introductions to Deno already.
Below are the topics that we will cover, feel free to follow along or jump directly to the topic of interest if you prefer:

Deno:

Angular:

Repo

 

What is Deno

Deno has been created by Ryan Dahl, the same creator of Node.js.

Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust.

If you are already familiar with Node, then Deno is able to do exactly the same things, but faster.

Deno can be seen as a way to rewamp Node.js, solving different aspects that the same Ryan considered his "regrets".
Below is his talk at the JSConf EU 2018, where he exposes these points:

Install Deno

We can choose to use a package manager or execute directly a command in the shell.

Install via command

With Shell (Mac):

curl -fsSL https://deno.land/x/install/install.sh | sh
Enter fullscreen mode Exit fullscreen mode

With PowerShell (Windows):

iwr https://deno.land/x/install/install.ps1 -useb | iex
Enter fullscreen mode Exit fullscreen mode

 

Install via Package Manager

With Homebrew (Mac):

brew install deno
Enter fullscreen mode Exit fullscreen mode

With Chocolatey (Windows):

choco install deno
Enter fullscreen mode Exit fullscreen mode

 

After Deno is downloaded and setup locally, run deno --version in the shell to verify the installation. We should get a similar output:

$ deno --version
deno 1.0.3
v8 8.4.300
typescript 3.9.2
Enter fullscreen mode Exit fullscreen mode

 
If we want an overview about the available commands, we can invoke the instruction deno --help, showing all the available sub-commands.
We can even get further details for each single command simply appending the --help flag, like: deno run --help

Alt Text

 

Available modules

Deno provides a list of standard modules, reviewed by the core team and guaranteed to work with the specific Deno version. These standard modules are hosted at https://deno.land/std and provide functionalities for most of the basic tasks like: uuid generation, http calls and file system access, for instance.

Aside these, deno.land website also provides a public hosting service for third party modules that are compatible with Deno at deno.land/x.
We can search among an exhaustive collection of modules.

Alt Text

 

Create a Server

Now that everything is in place, let's start writing some code. Define a root folder for your server:

mkdir webServer && cd webServer 
Enter fullscreen mode Exit fullscreen mode

 

Server.ts

Create a server.ts file.

💡 Note: we can use plain JavaScript instead of typescript, but we would lose many of the benefits that typescript offers. Moreover, being Deno written on top of Rust and Typescript, it compiles directly .ts files for us.

Use the standard http module

To create an HTTP server we could import the server.ts file from the http standard module:

import { serve } from "https://deno.land/std@0.125.0/http/server.ts";

const server_port = 5400;

function req_handler(req: Request): Response {
  console.log("\nReceived a request...\n");
  const body = JSON.stringify({ message: "I am a DENO server 🦕" });
  return new Response(body, {
    status: 200,
    headers: {
      "content-type": "application/json; charset=utf-8",
    },
  });
}

serve(req_handler, { port: server_port})
console.log("Listening on PORT: ", server_port);
Enter fullscreen mode Exit fullscreen mode

 

💡 Note: you might have noticed that we import from a url here rather than from a local path.
This is a new concept in Deno, we don't need to have the packages already installed locally. We can import their latest version and cache it, making it available even while offline. Deno uses ES Modules to import packages, where from Node uses npm. This translates in the absence of the node_modules folder and package.json file (we don't have any trace of them in our webServer folder). With Deno we directly import the packages we want through its url.

Use third party module

Alternatively we can opt for oak, a middleware framework for Deno's http server, including a router middleware. This middleware framework is inspired by Koa, therefore already familiar to many Node.js developers. For our mock server I decided to use oak.

import { Application } from "https://deno.land/x/oak/mod.ts";
import { oakCors } from "https://deno.land/x/cors/mod.ts";
import router from "./src/routes.ts";

const port = 8280;
const app = new Application();

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

app.addEventListener("listen", ({ hostname, port, secure }) => {
    console.log(`--- Listening on: ${secure ? "https://" : "http://"}${
        hostname ?? "localhost"
        }:${port}`
    );
});
await app.listen({ port });
Enter fullscreen mode Exit fullscreen mode

 
If you already used Express the code above should be already very familiar. After creating an instance of the Application class, we can stack multiple middleware using the use() method and then activate the server (listen() method), waiting for incoming requests.

CORS

We can define CORS for our application otherwise we would get a client-side error every time we try to reach our server from the Angular app. Deno provides a cors module (https://deno.land/x/cors/mod.ts) with default settings that already capture many common cases. We can enable CORS with the following call:

app.use(oakCors());
Enter fullscreen mode Exit fullscreen mode

The default configuration, hence without parameters like in the snippet above, translates in the following set of options:

{
  "origin": "*",
  "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
  "preflightContinue": false,
  "optionsSuccessStatus": 204
}
Enter fullscreen mode Exit fullscreen mode

 

Routes.ts

As our mock server is pretty simple, I decided to create just a folder src to host all the business logic and keep it separated from the server.ts file.

The routes file contains all the endpoints that we want to expose to the Angular client and in our case implement the classic CRUD operations.

import { Router } from "https://deno.land/x/oak/mod.ts";
import {
    getAllEmployees, getEmployeeById, updateEmployee, addEmployee, deleteEmployee
} from "./employeeApis.ts";

const router = new Router();

router.get("/employees", getAllEmployees)
    .get("/employees/:id", getEmployeeById)
    .put("/employees/:id", updateEmployee)
    .post("/employees", addEmployee)
    .delete("/employees/:id", deleteEmployee);

export default router;
Enter fullscreen mode Exit fullscreen mode

 

Employee.ts

We need to define a generic model for our domain. Here we design an Employee object with some static data and no database storage, as it would be beyond the course scope, focusing on Angular and client side development only.

export interface Employee {
    id: number;
    firstname: string;
    lastname: string;
    email?: string;
}

export const EmployeeData: Employee[] = [
    { id: 1, firstname: 'Larry', lastname: 'Potter', email: 'larry.potter@hotmail.com' },
    { id: 2, firstname: 'Mara', lastname: 'Croft', email: 'mara.croft@gmail.com' },
    { id: 3, firstname: 'Thomas', lastname: 'Müller', email: 'thomas123@gmail.com' },
    { id: 5, firstname: 'Karl', lastname: 'Fritz', email: 'Karl_great@microsoft.com' },
    { id: 6, firstname: 'Paolo', lastname: 'Rossi' }
];
Enter fullscreen mode Exit fullscreen mode

 

EmployeeApis.ts

In this file we implement the real logic behind each endpoint. Any data mutation will affect the local data structure EmployeeData, seen above.
The code is very simple and self-explanatory therefore I won't go in detail about it.

import { EmployeeData, Employee } from './employee.ts';

// Returns all available employees
export const getAllEmployees = ({ response }: { response: any }) => {
    response.body = EmployeeData;
};

// Returns one employee by its Id or 404 if not found
export const getEmployeeById = ({ params, response }: { params: { id: string }; response: any }) => {
    const selectedEmployee: Employee | undefined = EmployeeData.find((employee) =>
        employee.id === +params.id
    );
    if (selectedEmployee) {
        response.status = 200;
        response.body = selectedEmployee;
    }
    else {
        response.status = 404;
        response.body = [];
    }
};

// Add a new employee to the list
export const addEmployee = async (
    { request, response }: { request: any; response: any },
) => {
    if (!request.hasBody) {
        response.status = 400;
    } else {
        const newEmployee: Employee = await request.body();

        newEmployee.id = getNextEmployeeId();
        EmployeeData.push(newEmployee);
        response.status = 201;
    }
};

//Provides the next number to be used as employee Id
function getNextEmployeeId(): number {
    let maxId = 1;
    EmployeeData.forEach(p => {
        maxId = Math.max(p.id, maxId);
    });
    return maxId + 1;
}

// Removes an employee by its Id or 404 if not found
export const deleteEmployee = (
    { params, response }: { params: { id: string }; response: any },
) => {
    const targetId = +params.id;
    const newEmployeeList = EmployeeData.filter(x => x.id !== targetId);
    if (newEmployeeList.length < EmployeeData.length) {
        replaceCollection(EmployeeData, newEmployeeList);
        response.status = 200;
    } else {
        response.status = 404;
    }
};

// Updates en existing employee
export const updateEmployee = async (
    { params, request, response }: {
        params: { id: string };
        request: any;
        response: any;
    },
) => {
    const targetId = +params.id;
    let employeeToUpdate: Employee | undefined = EmployeeData.find((employee) =>
        employee.id === targetId
    );
    if (employeeToUpdate) {
        const body = await request.body();
        const newEmployeeData: Employee = body.value;

        let updatedData = EmployeeData.map((e: Employee) => {
            return e.id === targetId ? { ...e, ...newEmployeeData } : e;
        });

        replaceCollection(EmployeeData, updatedData);
        response.status = 200;
    } else {
        response.status = 404;
    }
};

// Replaces the employee data structure with a new collection
function replaceCollection(originalData: Employee[], newData: Employee[]) {
    originalData.splice(0, originalData.length);
    originalData.push(...newData);
}
Enter fullscreen mode Exit fullscreen mode

 

Start the server

Now that we created all the needed files, it is time to start the server. Execute the following command in the shell from the path hosting your server file:

deno  run --allow-net server.ts
Enter fullscreen mode Exit fullscreen mode

⚠️ Note: if you didn't add the Deno install root ($HOME/.deno/bin) to your environment variables, then you have to prepend the file path to server.ts

 
By running the command, different modules are downloaded, but no folder inside our solution is created for them.

Compile file:///.../server.ts
Download https://deno.land/x/oak/mod.ts
Download https://deno.land/x/oak/application.ts
Download https://deno.land/x/oak/context.ts
Download https://deno.land/x/oak/cookies.ts
Download https://deno.land/x/oak/httpError.ts
Download https://deno.land/x/oak/middleware.ts
Download https://deno.land/x/oak/request.ts
Download https://deno.land/x/oak/response.ts
Download https://deno.land/x/oak/router.ts
Download https://deno.land/x/oak/send.ts
Download https://deno.land/x/oak/types.ts
Download https://deno.land/x/oak/deps.ts
Download https://deno.land/x/oak/keyStack.ts
Download https://deno.land/x/oak/tssCompare.ts
Download https://deno.land/std@v1.0.0-rc1/http/server.ts
...
Enter fullscreen mode Exit fullscreen mode

These modules are cached from now on and we do not need to download them again, unless we explicitly want to, using the --reload option, for instance. By default, the cached modules are stored in Deno's base directory: $HOME/.deno (DENO_DIR), but we can change this location if we need, typically in the case of a production environment.

DENO_DIR contains the following files and directories:

Alt Text

 

💡 Note: in Deno, you need to give explicit permissions before running a program. In the command above we had to grant network access with the option: --allow-net

If we omit this option, we get the following error after downloading all the modules:

error: Uncaught PermissionDenied: network access to "127.0.0.1:8280", run again with the --allow-net flag
    at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
    at Object.sendSync ($deno$/ops/dispatch_json.ts:72:10)
    at Object.listen ($deno$/ops/net.ts:51:10)
    at listen ($deno$/net.ts:164:18)
    at Application.serve (server.ts:261:20)
    at Application.listen (application.ts:106:31)
    at server.ts:18:11
Enter fullscreen mode Exit fullscreen mode

And that was all we need to create a simple http server to use as a mock for our client application. Let's create now an Angular project that uses our REST APIs.

Debugging

Deno supports V8 Inspector Protocol. We can debug Deno programs with Chrome DevTools or other clients that support the protocol.

As most probably we are using Visual Code to implement our Angular application, let's see how we can debug the Deno server directly from our IDE. An official plugin is currently under construction, but for the time being we can create a launch.json file:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Deno",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "runtimeExecutable": "deno",
      "runtimeArgs": ["run", "--inspect-brk", "-A", "server.ts"],
      "port": 9229
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

💡 Note: if you named your script file differently than server.ts you have to adapt the last item of "runtimeArgs" accordingly.

With the configuration above, VS Code debugger will run at: 127.0.0.1:9229 and intercept all the breakpoints we set.

More about Deno

If you are interested in knowing more about Deno, I recommend the official blog post about the v 1.0 release.

Keep also an eye on the Deno Cheat Sheet as it is a great resource to have always an overview about all available commands.


 

Create an Angular service

For the Angular part, I will describe only the http service calling our REST server. All the code is available on the Github repo anyway and you can download the whole project.

If you don't have already an existing Angular application and you need instructions on how to create one, have a look at my post about it.

EmployeeService.ts

Thanks to schematics, generate files in Angular is very easy:

ng g service employee
Enter fullscreen mode Exit fullscreen mode

 
This command creates the EmployeeService.ts and its unit test file. In the service, we define the methods implementing the CRUD operations and that will call the endpoints of the Deno server that we implemented before.

import { Employee } from './../model/employee.model';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { Observable ,  of ,  throwError as _throw } from 'rxjs';
import { catchError, delay, map } from 'rxjs/operators';
import { environment } from '../../../environments/environment';

@Injectable()
export class EmployeeService {

    constructor(private http: HttpClient) { }

    getEmployees(): Observable<Employee[]> {
        return this.http
          .get<Employee[]>(`${environment.apiBaseUrl}/employees`)
          .pipe(catchError((error: any) => _throw(error)));
      }


    getEmployee(id: number): Observable<Employee> {
      return this.http
        .get<Employee>(`${environment.apiBaseUrl}/employees/${id}`)
        .pipe(catchError((error: any) => _throw(error)));
    }

      createEmployee(payload: Employee): Observable<Employee> {
        return this.http
          .post<Employee>(`${environment.apiBaseUrl}/employees`, payload)
          .pipe(catchError((error: any) => _throw(error)));
      }

      updateEmployee(payload: Employee): Observable<Employee> {
        return this.http
          .put<Employee>(`${environment.apiBaseUrl}/employees/${payload.id}`, payload)
          .pipe(catchError((error: any) => _throw(error)));
      }

      removeEmployee(payload: Employee): Observable<any> {
        return this.http
          .delete<any>(`${environment.apiBaseUrl}/employees/${payload.id}`)
          .pipe(catchError((error: any) => _throw(error)));
      }

}

Enter fullscreen mode Exit fullscreen mode

 

Environment.ts

In the file environment.ts we can save the base url for the server and eventually other configuration keys. environment.prod.ts, reserved for prod builds, typically has keys with different values, to target the production server instead of the staging one.

export const environment = {
  production: false,
  apiBaseUrl: 'http://localhost:8280'
};
Enter fullscreen mode Exit fullscreen mode

 

Conclusion

As we saw, it is very easy to create a web server with Deno and use it as a mock for our client application.

This architecture is very convenient because it allows to decouple our web app from the server mocks. We can execute real network calls from our client without need to apply any change to our Angular app before deploying it to production.

 

Github Repo

The sample code (Angular and Deno server) is available on Github: https://github.com/pacoita/deno-api-mock

Latest comments (3)

Collapse
 
ashish8947 profile image
Ashish Sharma

Thanks for the article! it looks good Check this article too how to build a CLI tool using Deno
loginradius.com/engineering/blog/b...

Collapse
 
juanbenitopr profile image
Juan Benito

Thanks for the article! I am curious there are so much information and many articles about deno these days, do you think there are good arguments to switch from nodejs or is it only the new trend technology?. Thanks!

Collapse
 
paco_ita profile image
Francesco Leardini

I would not switch my "production level" app to Deno yet. It is still too young to be sure you can bet on it. However I would start using Deno for my smaller projects or POCs instead of Nodejs, as Deno filled many of the tech gaps still affecting nodejs (the author "regrets"). I did not run benchmarks, but for the experience I made with Deno it looks a very promising technology.