Introduction
In this series we will setup an express server using Typescript
, we will be using TypeOrm
as our ORM for querying a PostgresSql Database, we will also use Jest
and SuperTest
for testing. The goal of this series is not to create a full-fledged node backend but to setup an express starter project using typescript which can be used as a starting point if you want to develop a node backend using express and typescript.
Overview
This series is not recommended for beginners some familiarity and experience working with nodejs
, express
, typescript
and typeorm
is expected. In this post which is part four of our series we will : -
- Use the
Todo
entity to query our database. - Create a
Todo
Service. - Use the
Todo
Service in ourTodo
controller.
Step One: Query our database
In our todo.controller.ts
lets now remove the hard-coded todos
and query our database like so :
import { Request, Response } from 'express';
import { AppDataSource } from '../../datasource';
import { Todos } from './todos.entity';
class TodoController {
async getAllTodos(req: Request, res: Response) {
try {
const todos = await AppDataSource.getRepository(Todos)
.createQueryBuilder('todos')
.getMany();
return res.status(200).json({
status: true,
statusCode: 200,
todos,
});
} catch (error) {
return res.status(500).json({
status: false,
statusCode: 500,
message: 'Something unusual Happened',
});
}
}
async getTodoById(req: Request, res: Response) {
try {
const { id } = req.params;
const todo = await AppDataSource.getRepository(Todos)
.createQueryBuilder('todos')
.where('todos.id = :id', { id })
.getOne();
if (!todo) {
return res.status(404).json({
status: false,
statusCode: 404,
message: `todo not found for id - ${id}`,
});
}
return res.status(200).json({
status: true,
statusCode: 200,
todo,
});
} catch (error) {
return res.status(500).json({
status: false,
statusCode: 500,
message: 'Something unusual Happened',
});
}
}
}
export const todosController = new TodoController();
For querying the database I use the queryBuilder
pattern, the createQueryBuilder
function takes in the table name, make sure you pass in the right name. The try-catch blocks are necessary to handle errors, thrown by our code, or by the orm library, etc. in the up coming tutorials we will create an async handler middleware, that will take care of all this, and then we can get rid of all these try-catch blocks.
Step 2: Setup Todo service
Writing all our queries and database related logic in separate service methods as opposed to writing them in the controller is one of the best practices:
- Writing code that does one job (an atomic unit of code) is easily testable, readable and scalable in the long run.
- We have re-usable functions that can be used in many controllers, other services throughout the project.
- When testing our code, we can easily mock our services, as opposed to mocking our request handlers. We can also unit test our service functions.
You can notice in each controller code we have to call the getRepository
function we can just move this piece of code in the entity file itself, or outside our controller class. But given the fact that we are using Typescript lets create a BaseService class. Under src/utils
create a new file BaseService.ts
and paste the following :
import { EntityTarget, Repository } from 'typeorm';
import { AppDataSource } from '../datasource';
export class BaseService<T> {
repository: Repository<T>;
constructor(entity: EntityTarget<T>) {
this.repository = AppDataSource.getRepository(entity);
}
}
BaseService
is a generic class, its sole job is to create a repository for us, so that every service class that extends it has the this.respository
property. As our project grows we can move a lot of repetitive code in this base class. Now lets create our todos.service.ts
inside src/api/todos.service.ts
:
import { BaseService } from '../../utils/BaseService';
import { Todos, TodoStatus } from './todos.entity';
class TodosService extends BaseService<Todos> {
constructor() {
super(Todos);
}
getAllTodos() {
return this.repository.createQueryBuilder('todos').getMany();
}
getTodoById(todoId: string) {
return this.repository
.createQueryBuilder('todos')
.where('todos.id = :id', { id: todoId })
.getOne();
}
createTodo(text: string, status: TodoStatus) {
return this.repository
.createQueryBuilder()
.insert()
.into(Todos)
.values({
text,
status,
})
.returning('*')
.execute();
}
}
export const todosService = new TodosService();
In our constructor we call super(Todo)
this invokes the BaseService
class constructor and passes our entity to it. We have also added a new createTodo
function in our service.
Step Three: Use service in our controller
Now we will use our service methods in the controller file:
import { Request, Response } from 'express';
import { todosService } from './todos.service';
class TodoController {
async getAllTodos(req: Request, res: Response) {
try {
const todos = await todosService.getAllTodos();
return res.status(200).json({
status: true,
statusCode: 200,
todos,
});
} catch (error) {
return res.status(500).json({
status: false,
statusCode: 500,
message: 'Something unusual Happened',
});
}
}
async getTodoById(req: Request, res: Response) {
try {
const { id: todoId } = req.params;
const todo = await todosService.getTodoById(todoId);
if (!todo) {
return res.status(404).json({
status: false,
statusCode: 404,
message: `todo not found for id - ${todoId}`,
});
}
return res.status(200).json({
status: true,
statusCode: 200,
todo,
});
} catch (error) {
return res.status(500).json({
status: false,
statusCode: 500,
message: 'Something unusual Happened',
});
}
}
async createTodo(req: Request, res: Response) {
try {
const { text, status } = req.body;
const newTodo = await todosService.createTodo(text, status);
return res.status(201).json({
status: true,
statusCode: 201,
todo: newTodo.raw,
});
} catch (error) {
return res.status(500).json({
status: false,
statusCode: 500,
message: 'Something unusual Happened',
});
}
}
}
export const todosController = new TodoController();
Lets register an endpoint in our todos.router.ts
:
addRoutes(): void {
this.router.get('/', todosController.getAllTodos);
this.router.post('/', todosController.createTodo);
this.router.get('/:id', todosController.getTodoById);
}
Now you can start your webserver by running npm run dev
and test your endpoints. I personally use postman, but for such small projects I use REST client for vscode and here is my api.http
-
GET http://localhost:8080/api/todos
###
GET http://localhost:8080/api/todos/:todoId
###
POST http://localhost:8080/api/todos
Content-Type: application/json
{
"text": "Walk for 30 minutes",
"status": "pending"
}
Summary
Well then we have finished setting up our service and we also used it in our controller. All the code for this tutorial can be found under the feat/querying-database
branch here. In the next tutorial we will get rid of all the try and catch repetitive code by setting a global Error handler and an async middleware until next time PEACE.