Full-Stack To-Do List application with Node, NestJS, Angular 2+, Postgres, Liquibase and Docker (Part 7 of 9)

Taylor Buckner
4 min readOct 23, 2020

Back to the API

The Branch

git checkout origin api-todo-module-start

ToDo Module

Let’s shift our focus back to the API and get our ToDo CRUD operations ironed out. Earlier I mentioned Modules being a way of organizing our code and features in both NestJS and Angular. Now that we’re working on a new feature, this is a perfect opportunity to create a new Module.

We can do that with:

# In ./api
npx nest generate module to-do

In addition to this ToDoModule we also are going to need some Models (a definition of the shape of our entities), a Controller and a Service.

We can get a couple of those knocked out with:

# In ./api
npx nest generate service to-do && npx nest generate controller to-do

Pretty slick, huh?

NestJS’s CLI even updates the module for us:

Models

We’re going to have two models in our ToDo Feature Module for now, a DTO model and a Model. We can create those manually by creating a new directory under ./api/src/todo called models, and adding two new files there:

  1. to-do.dto.ts
  2. to-do.model.ts

to-do.dto.ts

export class ToDoDto {
description: string;
}

to-do.model.ts

import { ToDoDto } from './to-do.dto'export interface ToDoModel extends ToDoDto {
id: number;
}

There are other approaches, but this should work fine. The DTO will serve as an interface for all Create and Update operations in our CRUD acronym, whereas the Model will help us model the Read responses

Service

The service, as mentioned a few times now, is our heavy lifter 💪. He’s going to talk to our Postgres Database and do the things. We’ll do this by injecting the DATABASE_CONNECTION_TOKEN in the service and using the .query() method.

Since we’re injecting the DATABASE_CONNECTION_TOKEN, we’ll also need to import the DatabaseModule into the ToDoModule like so:

import { Module } from '@nestjs/common';
import { DatabaseModule } from 'src/database/database.module';
import { ToDoController } from './to-do.controller';
import { ToDoService } from './to-do.service';
@Module({
imports: [
DatabaseModule,
],
controllers: [ToDoController],
providers: [ToDoService]
})
export class ToDoModule {}

Replace the contents of to-do.service.ts with the following:

import { Injectable, Inject } from '@nestjs/common';
import { Client } from 'pg';
import { DATABASE_CONNECTION_TOKEN } from 'src/database/database.providers';
import { ToDoDto } from './models/to-do.dto';
import { ToDoModel } from './models/to-do.model';
@Injectable()
export class ToDoService {
constructor(
@Inject(DATABASE_CONNECTION_TOKEN) private connectedClient: Client
) { }
public async getToDoItems(): Promise<Array<ToDoModel>> {
const query = 'SELECT * FROM app_schema.todo_items ORDER BY id ASC';
const result = await this.connectedClient.query(query);
return result.rows;
}
public async getToDoItem(id: number): Promise<Array<ToDoModel>> {
const query = 'SELECT * FROM app_schema.todo_items WHERE id = $1';
const result = await this.connectedClient.query(query, [id]);
return result.rows;
}
public async addToDoItem(newToDoItem: ToDoDto): Promise<void> {
const query = 'INSERT INTO app_schema.todo_items (description) VALUES ($1)';
await this.connectedClient.query(query, [newToDoItem.description]);
}
public async updateToDoItem(id: number, nextToDoItem: ToDoDto): Promise<void> {
const query = 'UPDATE app_schema.todo_items SET description = $2 WHERE id = $1';
await this.connectedClient.query(query, [id, nextToDoItem.description]);
}
public async removeToDoItem(id: number): Promise<void> {
const query = 'DELETE FROM app_schema.todo_items WHERE id = $1'
await this.connectedClient.query(query, [id]);
}
}

Controller

Last up is our Controller, which as mentioned above is our first line of communication with the outside world for this feature. We’ll make use of NestJS’s decorators to let the magic do its thing!

Replace the contents of that file with the following:

import { Controller, Get, Param, Post, Body, Delete, Put } from '@nestjs/common';
import { ToDoModel } from './models/to-do.model';
import { ToDoService } from './to-do.service';
import { ToDoDto } from './models/to-do.dto';
@Controller('todos')
export class ToDoController {
constructor(private toDos: ToDoService) {}

@Get()
public async getToDos(): Promise<Array<ToDoModel>> {
return this.toDos.getToDoItems();
}
@Post()
public async createToDo(@Body() nextToDoItem: ToDoDto): Promise<any> {
return this.toDos.addToDoItem(nextToDoItem);
}
@Get(':id')
public async getToDo(@Param('id') id): Promise<Array<ToDoModel>> {
return this.toDos.getToDoItem(id);
}
@Put(':id')
public async putToDo(@Param('id') id, @Body() nextToDoItem: ToDoDto): Promise<void> {
return this.toDos.updateToDoItem(id, nextToDoItem);
}
@Delete(':id')
public async deleteToDo(@Param('id') id): Promise<void> {
return this.toDos.removeToDoItem(id);
}
}

Recap

Great, so now we have:

  • A containerized Postgres Database
  • A containerized pgAdmin service wired up to our Postgres Database
  • Liquibase CLI installed and configured
  • Liquibase tracking our changes in the DB
  • A barebones NestJS API Application
  • A barebones Angular SPA Application
  • A single Changeset in our Changelog that creates our ToDo Table
  • Our API is wired up to our Postgres Database
  • Angular Material installed in our SPA app
  • CRUD operations supported on our API

Keep pushing! 🚚

--

--