Introduction

ROQ One Backend has a modular architecture. This means all classes are placed inside of modules with explicit boundaries and dependencies. Each module consists of three layers:

  • API layer

  • Logic layer

  • Data layer

The following image shows the interaction among these layers:

API layer

The API layer comprises the public interface of the module. For those using GraphQL, this is where you will implement resolvers with queries and mutations. For others who use REST, you will implement controllers instead.

GraphQL resolvers

src/{module}/resolvers

GraphQL uses resolvers with queries, mutations, and resolved fields. Here, ROQ One Backend uses the code-first approach of Nest.js.

๐Ÿ“– Nest.js - code-first approach

๐Ÿ“– Apollo Server Resolvers

Resolvers are using DTOs to define their input parameters and models to define their return values:

  @Mutation(() => AuthorModel)
  async updateAuthor(
    @Args({ name: 'id', type: () => ID }, ParseUUIDStringPipe) id: string,
    @Args({ name: 'author', type: () => AuthorUpdateDto })
    authorData: AuthorUpdateDto
  ): Promise<AuthorModel> { ... }
TYPESCRIPT

Input parameters (DTOs)

src/{module}/dtos

Data transfer objects (DTOs) describe the structure of the queriesโ€™ and mutationsโ€™ input parameters. Each variable can be decorated with validators from the class-validator library.

๐Ÿ“– class-validator documentation

The following example shows the AuthorCreateDto. The variable name has several validators which are automatically applied on the incoming data.

@InputType()
export class AuthorCreateDto {

  @Field({ nullable: true })
  @MinLength(3)
  @IsNotEmpty()
  @MaxLength(255)
  @IsString()
  @IsOptional()
  name?: string;
TYPESCRIPT

Return types (models)

src/{module}/models

Models represent the return values of resolvers. This is what will be returned when you run a GraphQL query or mutation. Models look similar to DTOs, but they serve a different purpose and donโ€™t use the class-validator.

๐Ÿ“– NestJS - resolvers

Example AuthorModel with id and name fields:

@ObjectType()
export class AuthorModel {
  @Field(() => ID)
  id: string;

  @Field({ nullable: true })
  name?: string;
TYPESCRIPT

Data-loaders

โžšnestjs-dataloader

Logic layer

Services

service is a class that contains the actual business logic, which sits between the resolvers and the data layer. Services use dependency injection to retrieve their dependencies.

Example AuthorService:

@Injectable()
export class AuthorService {
  constructor(
    @InjectRepository(AuthorRepository)
    protected authorRepository: AuthorRepository,
    protected configService: ConfigService,
    protected utilityService: UtilityService
  ) {}

  public async create(authorInput: AuthorCreateDto): Promise<AuthorEntity> {
    const { ...authorData } = authorInput;
    const authorEntity = await this.authorRepository.create(authorData);

    return this.authorRepository.save(authorEntity);
  }
TYPESCRIPT

Data layer

Repositories

src/{module}/repositories

repository is a class that contains all the required queries and statements to interact with the database. Repositories always extend the BaseRepository, which inherits the underlying logic from TypeORMs Repository class.

๐Ÿ“– TypeORM - Repositories

Example AuthorRepository

@EntityRepository(AuthorEntity)
export class AuthorRepository extends BaseRepository<AuthorEntity> {
  buildSelectQuery(
    query?: AuthorFindQueryInterface
  ): SelectQueryBuilder<AuthorEntity> {
    return super.buildSelectQuery(query);
  }
}
TYPESCRIPT

Entities

src/{module}/entities

An instance of an entity represents exactly one record of a database table. These are created by TypeORM and must be mapped to model classes.

๐Ÿ“– TypeORM - Entities

Example AuthorEntity

@Entity({ name: 'author' })
export class AuthorEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ nullable: true, type: 'varchar' })
  name?: string;
TYPESCRIPT

Cross-layer

There are some helper classes that are used to structure data across the layers. These classes do not contain any logic.

  • src/{module}/constants - Helper class that holds the constants of the module

  • src/{module}/interfaces - All interfaces of the module

  • src/{module}/enums - All enums of the module