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