In the previous post we learnt how to start the project from scratch, create our first Provider and also to configure a very-basic HTTP endpoint.
In this post we will talk about organizing our project with the Repository pattern and splitting our code in three layers (top-down ordered):
- Controller Just the API definition and only one call to the handler.
- Group - Controller handler Holds the business logic. One+ function for each endpoint.
- Data Repository Each data model should have its own Repository
- Rethink Service Common Service with basic db operations. Mostly CRUD
- Rethink Connection Service Service for dynamic db connections creation. (Already explained in chapter 1)
Adding basic functions to Rethink Service
We must upgrade our RethinkService
class with some methods for CRUD operations, as in the previous post only the createTable()
function was created.
NOTE: All this code is part of rethink/rethink.service.ts file.
Inserting new data
/**
* Inserts data in the specified table
* @param tableName Table where insert data
* @param content Data to insert
*/
async insert(tableName:string, content:object): Promise<rethink.WriteResult> {
let result = await rethink
.table(tableName)
.insert(content)
.run(this.connection)
return result
}
With the previous function, we can insert any type of data in the RethinkDB server (the only requirement is to be an object parseable to JSON).
Organizing our datatypes. Model/Entity to the rescue
In the real-life, it's common each kind of data has specific business logic and also different endpoints.
If you've used an ORM previously like myBatis,Hibernate,Sequelize or TypeORM. You're probably familiar with the Entity or Model concept and the pattern involved, MVC.
An Entity it's just an object in your code wich is linked somehow with the database.
If we want to keep our code structured I suggest organize the project with this pattern.
Services exports modifications
In order to use some Modules created in the previous post, we need to export the services in their modules:
RethinkModule
import { Module } from '@nestjs/common';
import { RethinkController } from './rethink.controller';
import { RethinkService } from './rethink.service';
import { RethinkProvider } from './database.provider'
@Module({
imports: [],
controllers: [RethinkController],
providers: [RethinkService, RethinkProvider],
exports: [
RethinkProvider,
RethinkService
]
})
export class RethinkModule {}
In the previous post, we overrode the AppModule and declared the RethinkModule as the main one modifying the main.ts
file in the root directory.
For teaching purposes, this was a good idea for keeping the focus in the main idea and not in the module hierachy.
Now we need to get a hierarchy consistency starting from the main AppModule
, so let's modify the main.ts
file again:
import { NestFactory } from '@nestjs/core';
import { AppModule } from 'app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
Now our app it's broken because the AppModule has not any modules imported, let's learn how to deal with nestJS module hierachy making the modules RethinkModule
and MessageModule
to be imported by the AppModule:
app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from 'app.controller';
import { AppService } from 'app.service';
import { MessageModule } from './message/message.module';
import { RethinkModule } from 'rethink/rethink.module';
@Module({
imports: [
MessageModule,
RethinkModule
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
With this file organization we've achieved module isolation and a loosely-coupled application.
Working with entities
Let's start creating a new directory under src directory named message
./src/message
Message module
In this folder, we need to create a new module:
message/message.module.ts
import { Module } from '@nestjs/common';
import { RethinkModule } from 'rethink/rethink.module';
import { MessageController } from './message.controller';
import { MessageService } from './message.service';
@Module({
imports: [RethinkModule],
controllers: [MessageController],
providers: [MessageService],
})
export class MessageModule {}
In this module, we're importing RethinkModule
because it's the common module which provides an interface for dealing with data in the database. We are just creating an implementation of this functions.
Also, we are declaring the MessageService and the MessageController in advance wich will be coded lately.
Message Service
Within the Message Service class we are going to define all the methods required for working with our entity, providing an abstraction layer between the backend database and our business logic.
Let's start creating the message class (our Entity):
message/message.ts
export class Message {
author: string
message: string
constructor(author: string, message: string) {
this.author = author
this.message = message
}
}
Now we are ready to create our Message Service:
message/message.service.ts
import { Injectable, Inject } from "@nestjs/common";
import { RethinkService } from "rethink/rethink.service";
import { Message } from "./message";
const TABLE = "messages"
@Injectable()
export class MessageService {
private rethinkService: RethinkService
constructor(@Inject('RethinkService') service) {
this.rethinkService = service
}
/**
* Store a new message in the database
* @param message Message object to be stored
*/
async newMessage(message: Message) {
return await this.rethinkService.insert(
TABLE,
message
)
}
}
Message Controller
As usually, we are going to create the new Controller:
message/message.controller.ts
Dependency inversion with the RethinkService
constructor(private readonly messageService: MessageService) {}
POST method for creating new messages
@Post()
async newMessage(@Body() message: Message): Promise<string> {
let response = await this.messageService.newMessage(message)
.then(result => {
return result
})
.catch(error => {
return error
})
return response
}
Full code available here: GitHub