The REST API

Last updated 3 months ago

Good, so far you have a frontend working properly and some todos in your database. Now it is time to code a REST API to link them both.

To do so, you are going to create a controller. Controllers receive the HTTP requests and process them. They may call services in the background to help them do this. We will not study the services in this tutorial.

Create a new controller.

foal generate controller api --register
> Empty

Open the new generated file api.controller.ts in the src/app/controllers/ directory and replace its content.

import { Get, HttpResponseOK } from '@foal/core';
export class ApiController {
@Get('/todos')
getTodos() {
const todos = [
{ id: 1, text: 'My task 1' },
{ id: 2, text: 'My task 2' }
];
return new HttpResponseOK(todos);
}
}

Controllers have special methods that define the routes and their respective handlers. These functions are decorated by one of the decorators Get, Post, Patch, Put or Delete which define the http method and the path of the route.

In this case the controller responds with a 200 status and a mock data (the two fake todos).

Refresh your browser, you should see the two todos printed.

Now, we would like to return the todos stored in the database. Update the code as follows:

import { Get, HttpResponseOK } from '@foal/core';
import { getRepository } from 'typeorm';
import { Todo } from '../entities';
export class ApiController {
@Get('/todos')
async getTodos() {
const todos = await getRepository(Todo).find();
return new HttpResponseOK(todos);
}
}

If you refresh your browser, you should now see the tasks that we created through the command line.

Add the create and delete features.

@Post('/todos')
async postTodo(ctx: Context) {
// Create a new todo with the body of the HTTP request.
const todo = new Todo();
todo.text = ctx.request.body.text;
// Save the todo in the database.
await getRepository(Todo).save(todo);
// Return the new todo with the id generated by the database. The status is 201.
return new HttpResponseCreated(todo);
}
@Delete('/todos/:id')
async deleteTodo(ctx: Context) {
// Get the todo with the id given in the URL if it exists.
const todo = await getRepository(Todo).findOne({ id: ctx.request.params.id });
// Return a 404 Not Found response if no such todo exists.
if (!todo) {
return new HttpResponseNotFound();
}
// Remove the todo from the database.
await getRepository(Todo).remove(todo);
// Returns an successful empty response. The status is 204.
return new HttpResponseNoContent();
}

The Context objet, which is passed to each route handler, contains the express request object. This represents the HTTP request and has properties for the request query string, parameters, body, HTTP headers, and so on.

Now type a new todo in the input text and press Enter. The task appears in the todo list. Refresh the page, it should still be there. If you click on the checkbox, the todo is successfully deleted.

The last thing to know is how the ApiController is bound to the request handler. You defined so far routes in this controller but never registered the controller anywhere. This has been done automatically when you specified the --register option in the generator command.

Open the file app.controller.ts in src/app.

import { AuthenticateWithSessionAndCookie, controller } from '@foal/core';
import { ApiController, ViewController } from './controllers';
import { User } from './entities';
@AuthenticateWithSessionAndCookie(User)
export class AppController {
subControllers = [
controller('/', ViewController),
controller('/api', ApiController)
];
}

This controller is the main controller of the application. It is directly called when a request comes in. It may have sub-controllers that go in the controllers/ directory.

In that case, the --register flag directly added a new line to declare the ApiController as a sub-controller of AppController. The ViewController is in charge of rendering the index.html template.

The AuthenticateWithSessionAndCookie decorator is useless here as you will not use authentication in this tutorial. You can remove it if you want.