In this article, I will demonstrate how to create a sample GraphQL endpoint using NestJS as the backend service.
Choosing between REST and GraphQL comes with its own set of pros and cons, and the best option depends on your specific requirements. This article focuses on building a scalable and flexible NestJS backend with GraphQL while incorporating best practices. So do not expect the final output to be a simple CRUD example.
Create a NestJS application using NestCLI
Nest CLI can be used to create NestJS applications simply.
$ npm i -g @nestjs/cli
$ nest new project-name
npm libraries
In this application I will use the following libraries:
npm i dotenv
npm i @nestjs/graphql @nestjs/apollo @apollo/server graphql --force
npm i --save @nestjs/typeorm typeorm --force
npm i pg --save --force
Structure
You can use the Nest CLI to generate modules, but in this article, I will manually create the folder structure and classes for better control and customization. Here is the folder structure that I am going to use.

- common — To keep common classes like base.model, base.repository, base.service…etc.
- config — To keep config files
- modules — To keep modules separately. Each module contains graphql, service, repository, and models directories.
- util — To keep utility classes
Use environment variables with dotenv

Read this article for more details about using dotenv with NestJS.
Create the database module with multiple database support.
Instead of creating single DB support, here I will use how to make your application with multiple database support. Because this might be important in real-world applications. I am using Postgres as the DB.
Create a Postgres DB connection
import { TypeOrmModuleOptions } from "@nestjs/typeorm";
import { EnvUtils } from "src/util/env.utils";
export const DBS = {
CLIENT: EnvUtils.get('CLIENT_DB'),
STORES: EnvUtils.get('STORES_DB')
};
const COMMON_DB = {
host: EnvUtils.get('HOST'),
port: 5432,
username: EnvUtils.get('USERNAME'),
password: EnvUtils.get('PASSWORD'),
synchronize: false,
autoLoadEntities: true,
logging: false,
}
export const dbConnection: TypeOrmModuleOptions[] = [
{
type: 'postgres',
...COMMON_DB,
name: DBS.CLIENT,
database: DBS.CLIENT
},
{
type: 'postgres',
...COMMON_DB,
name: DBS.STORES,
database: DBS.STORES
}
]
COMMON_DB
defines shared settings for all PostgreSQL connections:
host, port, username, password
— database credentials.synchronize: false
— Prevents TypeORM from auto-creating tables (safe for production).autoLoadEntities: true
— Automatically loads entity classes (e.g., Client) from your codebase.logging:false
— Disables query logging.
dbConnection is an array of TypeOrmModuleOptions, defining two PostgreSQL connections:
- One for the CLIENT database (named and using DBS.CLIENT).
- One for the STORES database (named and using DBS.STORES).
Create database module
import { DynamicModule, Global, Module } from "@nestjs/common";
import { TypeOrmModule, TypeOrmModuleOptions } from "@nestjs/typeorm";
@Global()
@Module({})
export class DatabaseModule{
static forRoot(connection: TypeOrmModuleOptions[]): DynamicModule{
return{
module: DatabaseModule,
imports: connection.map((connection) =>
TypeOrmModule.forRoot(connection),
),
exports:[TypeOrmModule]
};
}
}
This will dynamically register multiple database connections.
- Global Scope:
Global()
ensures that once DatabaseModule is imported (e.g., in AppModule), its providers and exports are available throughout the application without needing to import it elsewhere.
- Dynamic Module:
forRoot(connection:TypeOrmModuleOptions[])
is a static method that returns a DynamicModule. This allows you to pass an array of TypeORM connection options (like dbConnection) and configure multiple database connections.
- TypeORM Integration:
connection.map((connection) => TypeOrmModule.forRoot(connection)
iterates over the provided connections (e.g., dbConnection) and registers each one with TypeORM using forRoot. Each connection becomes a named connection in the app.
- Exports:
exports: [TypeOrmModule]
makes the TypeOrmModule (and its providers, like repositories) available to other modules that need database access.
Create base.model.ts
This abstract class serves as a common base for all model classes, eliminating duplicate fields and ensuring cleaner, more maintainable code. You can add more common fields according to your requirements.
import { Field, ObjectType } from "@nestjs/graphql";
import { Column, CreateDateColumn, PrimaryGeneratedColumn } from "typeorm";
@ObjectType()
export abstract class BaseModel {
@Field()
@PrimaryGeneratedColumn('uuid')
id?: string;
@Field()
@CreateDateColumn({
type: 'timestamp',
name: 'created_at',
default: () => "CURRENT_TIMESTAMP(6)"
})
createdAt?: Date;
@Field({ nullable: true })
@Column({
name: 'created_by',
nullable: true,
type: 'uuid'
})
createdBy?: string;
@Field()
@CreateDateColumn({
type: 'timestamp',
name: 'updated_at',
default: () => "CURRENT_TIMESTAMP(6)"
})
updatedAt?: Date;
@Field({ nullable: true })
@Column({
name: 'updated_by',
nullable: true,
type: 'uuid'
})
updatedBy?: string;
}
There are some decorators like ObjectType()
and Field()
which are specific to GraphQL.
Create base.service.ts
For the common services, this serves as a generic base class for services. If you are expecting to have some common services, you can add them to this class.
import { Logger } from "@nestjs/common";
import { BaseRepository } from "../repositories/base.repository";
import { BaseModel } from "../models/base.model";
export class BaseService<T extends BaseModel> {
constructor(
protected readonly logger: Logger,
protected readonly repository: BaseRepository<T>
) {
this.logger.debug(`${this.constructor.name} initialized`);
}
async findAll(): Promise<T[]> {
this.logger.debug('Fetching all entities');
try {
const entities = await this.repository.find();
this.logger.debug(`Found ${entities.length} entities`);
return entities;
} catch (error) {
this.logger.error('Error fetching entities', error.stack);
throw error;
}
}
}
Create base.repository.ts
This is the common repository where you can define shared functions for reuse.
import { Logger } from "@nestjs/common";
import { Repository, FindOneOptions, FindManyOptions } from "typeorm";
import { BaseModel } from "../models/base.model";
export class BaseRepository<T extends BaseModel> {
constructor(
protected readonly logger: Logger,
protected readonly repository: Repository<T>
) {
this.logger.debug(`${this.constructor.name} initialized`);
}
/**
* Retrieves all entities matching the given options.
* @param options - TypeORM find options (e.g., where, order).
* @returns A promise resolving to an array of entities.
*/
async find(options?: FindManyOptions<T>): Promise<T[]> {
this.logger.debug('Fetching entities', options);
try {
const entities = await this.repository.find(options);
this.logger.debug(`Found ${entities.length} entities`);
return entities;
} catch (error) {
this.logger.error('Error fetching entities', error.stack);
throw error;
}
}
Adding common functions to the base service and base repository depends on your specific needs. I can’t definitively say it must be done this way.
Create the client module.
Let’s manually create our first module instead of using the Nest CLI. The client
module will include directories for models, services, repositories, and graphql. The GraphQL-related queries and mutations will be organized within the graphql
directory.
Create a model
This is my model class for Client
I have added a few sample fields. Since it extends BaseModel
all common fields from the base class are also available in this model.
import { Field, ObjectType } from "@nestjs/graphql";
import { BaseModel } from "src/common/models/base.model";
import { Column, Entity } from "typeorm";
@ObjectType()
@Entity('client')
export class Client extends BaseModel{
@Field({nullable: true})
@Column({
name: 'client_name',
type:'text',
nullable: true
})
client_name: string;
@Field({nullable: true})
@Column({
name: 'client_code',
type:'text',
nullable: true
})
client_code: string;
@Field({nullable: true})
@Column({
name: 'client_description',
type:'text',
nullable: true
})
client_description: string;
@Field()
@Column({
name: 'is_active',
type:'boolean',
})
is_active: boolean;
}
Create a service
Here, I’ve created a service interface and a service class. While it may not be strictly required, this interface pattern becomes useful in large-scale applications, enhancing testability, modularity, and the flexibility to swap implementations when needed.
import { Client } from "../models/client.model";
export abstract class ClientServiceInterface{
abstract getClients(): Promise<Client[]>;
}
import { Injectable, Logger } from "@nestjs/common";
import { ClientServiceInterface } from "./client.service.interface";
import { Client } from "../models/client.model";
import { ClientRepositoryInterface } from "../repositories/client.repository.interface";
import { BaseService } from "src/common/services/base.service";
@Injectable()
export class ClientService extends BaseService<Client> implements ClientServiceInterface{
constructor(private readonly clientRepository: ClientRepositoryInterface){
super(
new Logger(ClientService.name),
clientRepository,
);
}
async getClients(): Promise<Client[]> {
this.logger.log(ClientService.name);
return await this.clientRepository.getClients();
}
}
You can implement your own services while also leveraging the benefits of extending the base service.
Create a repository
You can use the same pattern for the repositories as well.
import { BaseRepository } from "src/common/repositories/base.repository";
import { Client } from "../models/client.model";
export abstract class ClientRepositoryInterface extends BaseRepository<Client>{
abstract getClients(): Promise<Client[]>;
}
import { BaseRepository } from "src/common/repositories/base.repository";
import { Client } from "../models/client.model";
import { Injectable, Logger } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { DBS } from "src/config/database/postgres.db.connection";
import { Repository } from "typeorm";
@Injectable()
export class ClientRepository extends BaseRepository<Client>{
constructor(
@InjectRepository(Client, DBS.CLIENT)
private readonly clientRepository: Repository<Client>,
){
super(new Logger(ClientRepository.name), clientRepository)
}
async getClients(): Promise<Client[]>{
const result = await this.clientRepository.find();
return result || [];
}
}
Since we are dealing with multiple DBs you need to specify the DB when injecting the repository.
Create the client resolver
GraphQL includes queries for fetching data and mutations for modifying data. In this example, I’ve created only a resolver for demonstration purposes, but we’ll add mutations later.
import { Query, Resolver } from "@nestjs/graphql";
import { Client } from "../models/client.model";
import { ClientServiceInterface } from "../service/client.service.interface";
import { InternalServerErrorException } from "@nestjs/common";
@Resolver(() => Client)
export class ClientResolver{
constructor(private readonly clientService: ClientServiceInterface){}
@Query(()=> [Client], {name: 'clients', nullable: false}, )
async getClients(): Promise<Client[]>{
try{
return await this.clientService.getClients();
}catch(e){
throw new InternalServerErrorException('Failed to fetch clients');
}
}
}
This is an example of a basic resolver.
- @Resolver(() => Client) — This is the resolver declaration, which indicates this class handles GraphQL operations related to the Client type.
- @Query(()=> [Client], {name: ‘getClients’, nullable: false}) — This query definition defines a GraphQL query named getClients that returns a non-nullable array of Client objects.
Create client module
Now I will create the client module manually since I didn’t use CLI.
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { Client } from "./models/client.model";
import { DBS } from "src/config/database/postgres.db.connection";
import { ClientRepositoryInterface } from "./repositories/client.repository.interface";
import { ClientRepository } from "./repositories/client.repository";
import { ClientServiceInterface } from "./service/client.service.interface";
import { ClientService } from "./service/client.service";
import { ClientResolver } from "./graphql/client.resolver";
@Module({
imports:[TypeOrmModule.forFeature([Client], DBS.CLIENT)],
providers:[
{
provide: ClientRepositoryInterface,
useClass: ClientRepository,
},
{
provide: ClientServiceInterface,
useClass: ClientService,
},
ClientResolver,
],
exports:[ClientServiceInterface],
})
export class ClientModule{}
As you can see, there are imports, providers, and exports as usual. There are a few more things you need to know. Here I used interface-based injection, which is more flexible.
I am using DBS.CLIENT as the connection name, which implies you might have multiple database connections (e.g., DBS.AUTH, DBS.ORDER). This is great for microservices or separation of concerns.
Exporting ClientServiceInterface is good for modularity, but ensure other modules actually need it. If only the resolver within this module uses it, you might not need to export anything.
Update app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ApolloDriverConfig, ApolloDriver } from '@nestjs/apollo';
import { GraphQLModule } from '@nestjs/graphql';
import { ClientModule } from './modules/client/client.module';
import { DatabaseModule } from './config/database/database.module';
import { dbConnection } from './config/database/postgres.db.connection';
@Module({
imports: [
DatabaseModule.forRoot(dbConnection),
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: true,
playground: false
}),ClientModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Now we need to update the app.module.ts file. Here is the breakdown.
- Database Setup
DatabaseModule.forRoot(dbConnection) — Initializes the database connection(s) using a custom DatabaseModule.
- GraphQL Setup
GraphQLModule.forRoot<ApolloDriverConfig>(…) - Configures GraphQL using the Apollo driver.
driver: ApolloDriver — Uses Apollo Server under the hood.
autoSchemaFile: true — Automatically generates the GraphQL schema (e.g., schema.gql) based on your resolvers and types.
playground: false — Disables the GraphQL Playground UI (you’d need a tool like Postman or a custom client to query the API).
- Feature Modules:
ClientModule — Imports your ClientModule, which brings in the ClientResolver, ClientService, and ClientRepository for client-related functionality.
Setup DB
Since I use Postgres, I created a DB called ClientDB and created a table called client. You can make it according to your model. And also do not forget to update the .env file.
PORT=3002
# DB connection
HOST=127.0.0.1
DB_PORT=5432
USERNAME=<username>
PASSWORD=<password>
# DBs
CLIENT_DB=clientDB
STORES_DB=storesDB
Test the application
Since I’ve disabled the playground, I access the endpoints via Postman. You can create a graphql request, and the url will be http://localhost:3002/graphql because I am using port 3002.

No comments: