Another ExpressJS API tutorial for 2021, part 05 — Configuring services
At the part five of this articles we are going to configure services which will communicate between controllers and the database

Hey all, if you are completely lost and arrived here, we are creating a series of small articles to build an API with ExpressJS and this is the part number 5. You can check the first article here.
Just as a quick overview, here is the series of articles that we are writing:
- Create your hello world ExpressJS API (Yes… we always need to start with a hello world)
- Configure our hello world application to use Typescript (TS is cool, believe me ;) )
- Create our first CRUD API endpoints for an user (Everyone wants a registered user in their applications, no?)
- Create and configure your controllers (We will talk about it after)
- Create and configure your services (What the h. is that?) We are here now ;)
- Middleware’s! (Yes, we need and use it everyday)
- End to end testings!
- Configuring a secure way to manage user permissions via API
- Putting all together into a Docker container
- Configuring a simple MongoDB to connect to our application
- Configuring permission level to the application
- Add logs with Winston!
Let’s get started! Last article we’ve created a controllers file that will handle what each configured route should do for an user resource. Now we aim to build services which will communicate with our databases and will be called at our controllers. That way, it will be easier to do code maintenance and, even if we will change the current database to another we will have no hard time on changing the code.
Services can abstract the usage of databases as well as to share common functions that other files might need.
It is also worth to say that all of the architecture of this project is a suggestion but it is not the only way you could do it and you shouldn’t be using it as the ‘master piece of truth’ since every case is a different case and we are dealing with a generic scenario that might work with tons of real life scenarios.
That all said, our Github branch is currently this. To focus only at the services at this point, we are going to add a very simple and temporary database that will be switched to a real one with MongoDB in the late articles.
Let’s create a folder called daos at our users folder and create a file called in.memory.dao.ts. The code will work like the following:
export class GenericInMemoryDao {
private static instance: GenericInMemoryDao;
users: any = [];constructor() {
console.log('Created new instance of GenericInMemoryDao');
}static getInstance(): GenericInMemoryDao {
if (!GenericInMemoryDao.instance) {
GenericInMemoryDao.instance = new GenericInMemoryDao();
}
return GenericInMemoryDao.instance;
}addUser(user: any) {
return this.users.push(user);
}getUsers() {
return this.users;
}getUserById(userId: string) {
return this.users.find((user: { id: string; }) => user.id === userId);
}putUserById(user: any) {
const objIndex = this.users.findIndex((obj: { id: any; }) => obj.id === user.id);
const updatedUsers = [
...this.users.slice(0, objIndex),
user,
...this.users.slice(objIndex + 1),
];
this.users = updatedUsers;
return `${user.id} updated via put`;
}patchUserById(user: any) {
const objIndex = this.users.findIndex((obj: { id: any; }) => obj.id === user.id);
let currentUser = this.users[objIndex];
for (let i in user) {
if (i !== 'id') {
currentUser[i] = user[i];
}
}
this.users = [
...this.users.slice(0, objIndex),
currentUser,
...this.users.slice(objIndex + 1),
];
return `${user.id} patched`;
}removeUserById(userId: string) {
const objIndex = this.users.findIndex((obj: { id: any; }) => obj.id === userId);
this.users = this.users.splice(objIndex, 1);
return `${userId} removed`;
}}
Update 2020–06–01: thanks Matthew for pointing that the splice method was incorrect. Now it’s updated :)
I will not go further to explain the code above since is just to have something to test our services at the end of this article. Remember that in the end of the series of article we are going to be using a real MongoDB with Docker and all the pieces will be connected to have real API working on your computer.
Never use a handmade “in memory” database in a live application, NEVER
Now we can create our services. As for what me made with a daos folder, let’s create a folder called services inside of the users folder and inside of it, create a file called user.services.ts
What we want to create here is an abstraction between a DAO file and the service itself. For abstracting it we are sure that we want the following methods to be always created:
- list: (limit: number, page: number) => any
- create: (resource: any) => string
- updateById: (resourceId: any) => string
- readById: (resourceId: any) => any
- deleteById: (resourceId: any) => string
- patchById: (resourceId: any) => string
We are basically using this pattern to every resource that we will create and instead of creating it directly to the service, we will be using a concept called interface. That will force us to implement these methods into our service and Typescript allows us do use this feature.
For now, let the user.services.ts empty and let’s go to the root folder of the project, and inside of the common folder, let’s create a folder called interfaces and the file called crud.interface.ts. Here is the final code:
export interface CRUD {
list: (limit: number, page: number) => any,
create: (resource: any) => string,
updateById: (resourceId: any) => string,
readById: (resourceId: any) => any,
deleteById: (resourceId: any) => string,
patchById: (resourceId: any) => string,
}We are just defining here the interface with the methods (functions) that we want to anyone who use this interface to have, setting the parameters and the abstract response. You can read more about interfaces here.
Now we can open the user.services.ts file and add the following:
import {CRUD} from '../../common/interfaces/crud.interface';
import {GenericInMemoryDao} from '../daos/in.memory.dao';export class UsersService implements CRUD {
private static instance: UsersService;
dao: GenericInMemoryDao;constructor() {
this.dao = GenericInMemoryDao.getInstance();
}static getInstance(): UsersService {
if (!UsersService.instance) {
UsersService.instance = new UsersService();
}
return UsersService.instance;
}create(resource: any) {
return this.dao.addUser(resource);
}deleteById(resourceId: any) {
return this.dao.removeUserById(resourceId);
};list(limit: number, page: number) {
return this.dao.getUsers();
};patchById(resource: any) {
return this.dao.patchUserById(resource)
};readById(resourceId: any) {
return this.dao.getUserById(resourceId);
};updateById(resource: any) {
return this.dao.putUserById(resource);
};}
And that’s it. Our service is now communicating with our generic dao and using all the methods that the CRUD interface is asking to. In the late articles we will go back to this service and change the implementation to use a real MongoDB with Mongoose.
Our service will be handling the calls to the current dao and we will change only this file to switch one database implementation to another one.
The missing cherry of this article is to update the user controller now. Open the user.controllers.ts file and update the code to call our service.
import express from 'express';
import {UsersService} from '../services/user.services';export class UsersController {
constructor() {
}listUsers(req: express.Request, res: express.Response) {
const usersService = UsersService.getInstance();
const users = usersService.list(100, 0);
res.status(200).send(users);
}getUserById(req: express.Request, res: express.Response) {
const usersService = UsersService.getInstance();
const user = usersService.readById(req.params.userId);
res.status(200).send(user);
}createUser(req: express.Request, res: express.Response) {
const usersService = UsersService.getInstance();
const userId = usersService.create(req.body);
res.status(201).send({id: userId});
}patch(req: express.Request, res: express.Response) {
const usersService = UsersService.getInstance();
usersService.patchById(req.body);
res.status(204).send(``);
}put(req: express.Request, res: express.Response) {
const usersService = UsersService.getInstance();
usersService.updateById(req.body);
res.status(204).send(``);
}removeUser(req: express.Request, res: express.Response) {
const usersService = UsersService.getInstance();
usersService.deleteById(req.params.userId);
res.status(204).send(``);
}}
Look that now we can talk a bit more about our structure:
- A routes file that is responsible only to define endpoints and who controls it (controllers)
- A controller file which is responsible to call a service which will do what the route is defined to do and to send a response to the client
- A service file that is responsible to “talk” to whatever database you might be wanting to use
- A specific DAO file that could be any database implementation, ORM, ODM, that you might need
- All of our functions are short and well defined and easy to read
That might be a bit of too much abstraction but once you start to have a huge project with hundreds of resources, services, then all of this breakdown strategy starts to easy it up your and your co-workers life. Again, its not a final strategy but one of several that I found useful in my working career so far.
If you will run npm start now you will be able to call your API requests with Postman or Insomnia and play with it. Remember that we are not controlling any type of request and every time you will restart the API all the data will be gone. But we have enough to test the routes, controllers and services with more useful data now.
Here are some requests CURL samples:
Create a new user:
curl --location --request POST 'localhost:3000/users' \
--header 'Content-Type: application/json' \
--data-raw '{
"username":"mytestingUser",
"password":"amazingMediumArticle"
}'List users:
curl --location --request GET 'localhost:3000/users' \
--header 'Content-Type: application/json' \Patch an user:
curl --location --request PATCH 'localhost:3000/users/3' \
--header 'Content-Type: application/json' \
--data-raw '{
"username":"mytestingUser2",
"password":"amazingMediumArticle",
"id":3
}'Put an user:
curl --location --request PUT 'localhost:3000/users/3' \
--header 'Content-Type: application/json' \
--data-raw '{
"username":"mytestingUser2",
"password":"amazingMediumArticle4",
"id":3
}'Delete an user:
curl --location --request DELETE 'localhost:3000/users/3' \
--header 'Content-Type: application/json' \
--data-raw ''Once again, we are not controlling id, body requests and others. The main purpose of this article is connect a controller, services and database into the routes and not to implement Business Rules.
That’s all for this article. The final version of this article as a code can be found here.
At our next article we are going to introduce the Middlewares concept to the users routes and implement some basic business rules and usages of things that should happen before the controller is used. Have fun coding ;)
Thanks for reading and see you at the next article!
tips:
- the full code of this article is HERE
- don’t forget to use a nice app to test your API such as Postman or Insomnia
- I know you still didn’t… Read the ExpressJS documentation!
- Book suggestion: Clean Code and again Design Patterns
- Brazilian? PT-BR Book suggestion: Clean Code and Design Patterns
- Complete project is here! (Includes even the non existing articles yet)
