Skip to main content
Version: v4


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 use 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.

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

import { Get, HttpResponseOK } from '@foal/core';

export class ApiController {

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 {
Context, Delete, Get, HttpResponseCreated, HttpResponseNoContent,
HttpResponseNotFound, HttpResponseOK, Post
} from '@foal/core';

import { Todo } from '../entities';

export class ApiController {

async getTodos() {
const todos = await 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.

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.

// Return the new todo with the id generated by the database. The status is 201.
return new HttpResponseCreated(todo);

async deleteTodo(ctx: Context) {
// Get the todo with the id given in the URL if it exists.
const todo = await Todo.findOneBy({ id: });

// Return a 404 Not Found response if no such todo exists.
if (!todo) {
return new HttpResponseNotFound();

// Remove the todo from the database.
await todo.remove();

// Returns an successful empty response. The status is 204.
return new HttpResponseNoContent();

The Context object, 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 is done in the app.controller.ts file, the entry point of your application.

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

import { controller, IAppController } from '@foal/core';

import { ApiController } from './controllers';

export class AppController implements IAppController {
subControllers = [
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.