Full-Stack To-Do List application with Node, NestJS, Angular 2+, Postgres, Liquibase and Docker (Part 7 of 9)
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:
to-do.dto.ts
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! 🚚