Skip to content

213020aumc/nestjs-basics

Repository files navigation

NestJS Basics — Learning Reference & Guide

A hands-on NestJS starter project exploring GraphQL (Apollo), modular architecture, and RBAC (Role-Based Access Control) patterns.


Table of Contents


Overview

This project is a learning sandbox for NestJS. It was scaffolded with the Nest CLI and progressively extended with feature modules that model an RBAC authentication system using GraphQL (Apollo Server, code-first approach).

All service methods currently return placeholder strings — the focus is on understanding NestJS architecture, module composition, and GraphQL wiring before connecting a real database.


Tech Stack

Layer Technology Version
Framework NestJS 11.x
Language TypeScript 5.x
Runtime Node.js ≥ 24.0
API GraphQL (Apollo Server 5 + @nestjs/graphql) 13.x
HTTP Platform Express (@nestjs/platform-express) 11.x
Build Tool SWC (via @swc/core) 1.x
Testing Jest + Supertest 29.x
Linting ESLint 9 (flat config) + Prettier 9.x

Prerequisites

Before you begin, make sure you have:

Node.js  ≥ 24.0.0
npm      ≥ 11.0.0

Verify with:

node -v
npm -v

Install the NestJS CLI globally (optional, but recommended):

npm i -g @nestjs/cli

Getting Started

# 1. Clone the repository
git clone https://github.com/213020aumc/nestjs-basics.git
cd nestjs-basics

# 2. Install dependencies
npm install

# 3. Start the dev server (hot-reload)
npm run start:dev

# 4. Open GraphQL Playground
#    Navigate to http://localhost:3000/graphql in your browser

# 5. Test the REST health-check
#    GET http://localhost:3000  →  "Hello World!"

Project Structure

nestjs-basics/
├── src/
│   ├── main.ts                        # App entry point — bootstraps NestFactory
│   ├── app.module.ts                  # Root module — imports all feature modules
│   ├── app.controller.ts              # Root REST controller (GET /)
│   ├── app.service.ts                 # Root service (returns "Hello World!")
│   ├── app.controller.spec.ts         # Unit test for AppController
│   │
│   ├── auth/                          # 🔐 Authentication module
│   │   ├── auth.module.ts
│   │   ├── auth.resolver.ts           # GraphQL resolver (queries + mutations)
│   │   ├── auth.service.ts            # Business logic (placeholder)
│   │   ├── auth.resolver.spec.ts      # Resolver unit test
│   │   ├── auth.service.spec.ts       # Service unit test
│   │   ├── dto/
│   │   │   ├── create-auth.input.ts   # @InputType for creating auth
│   │   │   └── update-auth.input.ts   # @InputType for updating auth (PartialType)
│   │   └── entities/
│   │       └── auth.entity.ts         # @ObjectType — GraphQL schema type
│   │
│   ├── users/                         # 👤 Users module
│   │   ├── users.module.ts
│   │   ├── users.resolver.ts
│   │   ├── users.service.ts
│   │   ├── users.resolver.spec.ts
│   │   ├── users.service.spec.ts
│   │   ├── dto/
│   │   │   ├── create-user.input.ts
│   │   │   └── update-user.input.ts
│   │   └── entities/
│   │       └── user.entity.ts
│   │
│   ├── roles/                         # 🎭 Roles module
│   │   ├── roles.module.ts
│   │   ├── roles.resolver.ts
│   │   ├── roles.service.ts
│   │   ├── roles.resolver.spec.ts
│   │   ├── roles.service.spec.ts
│   │   ├── dto/
│   │   │   ├── create-role.input.ts
│   │   │   └── update-role.input.ts
│   │   └── entities/
│   │       └── role.entity.ts
│   │
│   ├── permissions/                   # 🔑 Permissions module
│   │   ├── permissions.module.ts
│   │   ├── permissions.resolver.ts
│   │   ├── permissions.service.ts
│   │   ├── permissions.resolver.spec.ts
│   │   ├── permissions.service.spec.ts
│   │   ├── dto/
│   │   │   ├── create-permission.input.ts
│   │   │   └── update-permission.input.ts
│   │   └── entities/
│   │       └── permission.entity.ts
│   │
│   └── rbac/                          # 🛡️ RBAC (Role-Based Access Control) module
│       ├── rbac.module.ts
│       ├── rbac.resolver.ts
│       ├── rbac.service.ts
│       ├── rbac.resolver.spec.ts
│       ├── rbac.service.spec.ts
│       ├── dto/
│       │   ├── create-rbac.input.ts
│       │   └── update-rbac.input.ts
│       └── entities/
│           └── rbac.entity.ts
│
├── test/
│   ├── app.e2e-spec.ts                # End-to-end test
│   └── jest-e2e.json                  # Jest E2E config
│
├── nest-cli.json                      # Nest CLI configuration
├── tsconfig.json                      # TypeScript configuration
├── tsconfig.build.json                # TS config for production builds
├── eslint.config.mjs                  # ESLint flat config
├── .prettierrc                        # Prettier configuration
├── package.json                       # Dependencies & scripts
└── .gitignore

NestJS Core Concepts

1. Modules

Modules are the fundamental organizational unit in NestJS. Every app has at least one module (the root module).

// A module groups related functionality together
@Module({
  imports: [
    AuthModule,
    RbacModule,
    UsersModule,
    RolesModule,
    PermissionsModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Key takeaways:

  • imports — other modules this module depends on
  • controllers — REST controllers registered in this module
  • providers — services, resolvers, and other injectables
  • exports — providers that should be available to other modules that import this one

2. Controllers

Controllers handle incoming REST requests and return responses. They use decorators like @Get(), @Post(), etc.

@Controller() // Route prefix (empty = root "/")
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get() // Handles GET /
  getHello(): string {
    return this.appService.getHello();
  }
}

Note: In this project, GraphQL resolvers are used instead of controllers for most features. Controllers are only used for the root health-check endpoint.

3. Providers / Services

Services contain business logic and are decorated with @Injectable(). They are injected into controllers or resolvers via the constructor.

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

Key takeaway: Keep controllers/resolvers thin — delegate logic to services.

4. Dependency Injection

NestJS uses a powerful DI (Dependency Injection) system. When you declare a dependency in the constructor, Nest automatically resolves and injects it:

// NestJS sees the type hint and injects the registered UsersService instance
constructor(private readonly usersService: UsersService) {}

How it works:

  1. You register UsersService as a provider in UsersModule
  2. Any class within that module can request it via constructor injection
  3. Nest creates a singleton instance (by default) and shares it

5. Decorators

NestJS relies heavily on TypeScript decorators. Here's a quick reference:

Decorator Purpose Used On
@Module() Declares a module Class
@Controller() Declares a REST controller Class
@Injectable() Marks a class as injectable (DI) Class
@Get(), @Post() Maps HTTP methods to handler methods Method
@Resolver() Declares a GraphQL resolver Class
@Query() Maps a method to a GraphQL query Method
@Mutation() Maps a method to a GraphQL mutation Method
@Args() Extracts arguments from a GraphQL operation Parameter
@ObjectType() Defines a GraphQL object type Class
@InputType() Defines a GraphQL input type Class
@Field() Defines a field on a GraphQL type Property

GraphQL with NestJS

This project uses the code-first approach with Apollo Server.

Code-First Approach

Instead of writing .graphql schema files manually, you define your schema using TypeScript classes and decorators. NestJS auto-generates the SDL (Schema Definition Language) at runtime.

TypeScript Classes + Decorators  →  Auto-generated GraphQL Schema  →  Apollo Server

Object Types (Entities)

Entities define the shape of data returned by your API (equivalent to GraphQL type):

@ObjectType() // This class becomes a GraphQL type
export class User {
  @Field(() => Int, { description: 'Example field (placeholder)' })
  exampleField: number;
}

Input Types (DTOs)

DTOs (Data Transfer Objects) define the shape of data sent TO your API:

// CreateUserInput → used for mutations that create a user
@InputType()
export class CreateUserInput {
  @Field(() => Int, { description: 'Example field (placeholder)' })
  exampleField: number;
}

// UpdateUserInput → extends CreateUserInput with PartialType (all fields optional) + id
@InputType()
export class UpdateUserInput extends PartialType(CreateUserInput) {
  @Field(() => Int)
  id: number;
}

PartialType() pattern: Makes all inherited fields optional — perfect for update operations where you only send changed fields.

Resolvers

Resolvers are the GraphQL equivalent of controllers. They handle queries and mutations:

@Resolver(() => User)
export class UsersResolver {
  constructor(private readonly usersService: UsersService) {}

  // QUERY: Fetch all users
  @Query(() => [User], { name: 'users' })
  findAll() {
    return this.usersService.findAll();
  }

  // QUERY: Fetch single user by ID
  @Query(() => User, { name: 'user' })
  findOne(@Args('id', { type: () => Int }) id: number) {
    return this.usersService.findOne(id);
  }

  // MUTATION: Create a new user
  @Mutation(() => User)
  createUser(@Args('createUserInput') createUserInput: CreateUserInput) {
    return this.usersService.create(createUserInput);
  }

  // MUTATION: Update an existing user
  @Mutation(() => User)
  updateUser(@Args('updateUserInput') updateUserInput: UpdateUserInput) {
    return this.usersService.update(updateUserInput.id, updateUserInput);
  }

  // MUTATION: Delete a user
  @Mutation(() => User)
  removeUser(@Args('id', { type: () => Int }) id: number) {
    return this.usersService.remove(id);
  }
}

Feature Modules Breakdown

Each feature module follows the same consistent pattern:

feature/
├── feature.module.ts           # Module definition
├── feature.resolver.ts         # GraphQL resolver (queries + mutations)
├── feature.service.ts          # Business logic
├── feature.resolver.spec.ts    # Resolver tests
├── feature.service.spec.ts     # Service tests
├── dto/
│   ├── create-feature.input.ts # Input type for create mutation
│   └── update-feature.input.ts # Input type for update mutation
└── entities/
    └── feature.entity.ts       # GraphQL object type

Users Module

Manages user data. CRUD operations via GraphQL.

Operation Type GraphQL Field
Create user Mutation createUser
Get all users Query users
Get user by ID Query user
Update user Mutation updateUser
Delete user Mutation removeUser

Auth Module

Handles authentication logic (login, signup, token management).

Operation Type GraphQL Field
Create auth Mutation createAuth
Get all auth Query auth
Get auth by ID Query auth
Update auth Mutation updateAuth
Delete auth Mutation removeAuth

Roles Module

Manages user roles (e.g., Admin, User, Moderator).

Operation Type GraphQL Field
Create role Mutation createRole
Get all roles Query roles
Get role by ID Query role
Update role Mutation updateRole
Delete role Mutation removeRole

Permissions Module

Manages granular permissions (e.g., read:users, write:posts).

Operation Type GraphQL Field
Create permission Mutation createPermission
Get all permissions Query permissions
Get permission by ID Query permission
Update permission Mutation updatePermission
Delete permission Mutation removePermission

RBAC Module

Orchestrates role-based access control — links users, roles, and permissions.

Operation Type GraphQL Field
Create RBAC Mutation createRbac
Get all RBAC Query rbac
Get RBAC by ID Query rbac
Update RBAC Mutation updateRbac
Delete RBAC Mutation removeRbac

Available Scripts

Script Command Description
Dev server npm run start:dev Start with hot-reload (watch mode)
Start npm run start Start without watch mode
Debug npm run start:debug Start with debug + watch mode
Production npm run start:prod Run compiled JS from dist/
Build npm run build Compile TypeScript to JavaScript
Lint npm run lint Run ESLint with auto-fix
Format npm run format Run Prettier on all TS files
Unit tests npm run test Run all unit tests
Watch tests npm run test:watch Run tests in watch mode
Coverage npm run test:cov Run tests with coverage report
Debug tests npm run test:debug Run tests with Node inspector
E2E tests npm run test:e2e Run end-to-end tests
Prisma generate npm run prisma:generate Generate Prisma client (future use)
Prisma migrate npm run prisma:migrate Run Prisma migrations (future use)
Prisma seed npm run prisma:seed Seed the database (future use)

Testing

Unit Tests

# Run all unit tests
npm run test

# Run tests in watch mode (re-runs on file change)
npm run test:watch

# Run tests with coverage report
npm run test:cov

End-to-End Tests

npm run test:e2e

Test Structure

  • Unit tests live alongside their source files: *.spec.ts
  • E2E tests live in the test/ directory: *.e2e-spec.ts

Writing a Test (Example)

import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';

describe('AppController', () => {
  let app: TestingModule;

  beforeAll(async () => {
    app = await Test.createTestingModule({
      controllers: [AppController],
      providers: [AppService],
    }).compile();
  });

  describe('getHello', () => {
    it('should return "Hello World!"', () => {
      const appController = app.get(AppController);
      expect(appController.getHello()).toBe('Hello World!');
    });
  });
});

NestJS CLI Cheat Sheet

# Generate a new module
nest g module <name>

# Generate a new service
nest g service <name>

# Generate a new controller (REST)
nest g controller <name>

# Generate a new GraphQL resolver
nest g resolver <name>

# Generate a complete CRUD resource (prompts for transport layer)
nest g resource <name>
# → Choose "GraphQL (code first)" when prompted

# Generate with no test file
nest g service <name> --no-spec

# See all available generators
nest g --help

# Build the project
nest build

# Start the project
nest start --watch

Generating a New Feature Module (Example)

# This creates the full module structure in one command:
nest g resource products

# Select: GraphQL (code first)
# Select: Yes (generate CRUD entry points)
#
# Creates:
#   src/products/products.module.ts
#   src/products/products.resolver.ts
#   src/products/products.service.ts
#   src/products/dto/create-product.input.ts
#   src/products/dto/update-product.input.ts
#   src/products/entities/product.entity.ts
#   + spec files
#
# Also auto-updates app.module.ts to import ProductsModule!

Key Patterns to Remember

1. Module Registration Flow

Feature Module → Register in AppModule imports → Available app-wide

2. Standard CRUD Pattern (GraphQL Code-First)

Entity (@ObjectType)  →  DTOs (@InputType)  →  Service (@Injectable)  →  Resolver (@Resolver)  →  Module (@Module)

3. PartialType for Updates

// Instead of duplicating fields, extend the create input with PartialType
@InputType()
export class UpdateUserInput extends PartialType(CreateUserInput) {
  @Field(() => Int)
  id: number; // Only id is required; all other fields become optional
}

4. Constructor Injection

// DON'T manually instantiate services
const service = new UsersService();     // ❌ Wrong

// DO let NestJS inject them
constructor(private readonly usersService: UsersService) {}  // ✅ Correct

5. Singleton Services

By default, NestJS providers are singletons — the same instance is shared across the entire module. This is great for caching, connection pools, and shared state.


Common Pitfalls

Pitfall Solution
Forgot to add module to imports in AppModule Every feature module must be imported in the root module
Circular dependency between modules Use forwardRef(() => ModuleName)
Cannot determine a GraphQL type error Ensure every @Field() has an explicit type function: @Field(() => String)
Service not found / not injectable Check that it's listed in providers of its module
Duplicate imports (e.g., importing UsersModule twice) Clean up app.module.ts — each module should be imported only once
GraphQL Playground not loading Ensure autoSchemaFile is set in GraphQLModule.forRoot() config

Learning Roadmap

Track your progress as you extend this project:

  • Scaffold — Set up NestJS with TypeScript
  • Modules — Create feature modules (Auth, Users, Roles, Permissions, RBAC)
  • GraphQL — Integrate Apollo Server with code-first approach
  • Resolvers — Wire up CRUD resolvers for each module
  • DTOs — Define Input Types with PartialType pattern
  • Testing — Unit tests and E2E test setup
  • Database — Connect Prisma ORM (scripts already configured)
  • Validation — Add class-validator and class-transformer pipes
  • Auth Logic — Implement JWT authentication with Passport.js
  • Guards — Create authentication and authorization guards
  • RBAC Logic — Wire roles + permissions to guard endpoints
  • Error Handling — Custom exception filters
  • Logging — Structured logging with NestJS Logger
  • Pagination — Implement cursor/offset pagination for queries
  • Caching — Add caching layer
  • Deployment — Dockerize and deploy

Useful Resources

Resource Link
NestJS Official Docs https://docs.nestjs.com
NestJS GraphQL Docs https://docs.nestjs.com/graphql/quick-start
Apollo Server Docs https://www.apollographql.com/docs/apollo-server
TypeScript Decorators https://www.typescriptlang.org/docs/handbook/decorators.html
Prisma + NestJS https://docs.nestjs.com/recipes/prisma
NestJS Testing https://docs.nestjs.com/fundamentals/testing
NestJS Security (Guards) https://docs.nestjs.com/guards
GraphQL Playground http://localhost:3000/graphql (when server is running)

License

This project is MIT licensed.

About

Starter repository for practicing NestJS fundamentals including project setup, routing, and API building.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages