NestJS - Pytania Rekrutacyjne dla Node.js Backend Developera [2026]
NestJS to jeden z najpopularniejszych frameworków do budowy aplikacji backendowych w Node.js. Jeśli przygotowujesz się do rozmowy na stanowisko Node.js Backend Developer, ten przewodnik zawiera 42 pytania rekrutacyjne z odpowiedziami - od podstaw po zaawansowane tematy.
Spis treści
- Podstawy NestJS
- Moduły i Architektura
- Kontrolery i Routing
- Providery i Dependency Injection
- Middleware, Guards, Interceptory, Pipes
- Obsługa błędów
- Bazy danych
- Testowanie
- Zobacz też
Podstawy NestJS
Czym jest NestJS i jakie problemy rozwiązuje?
Odpowiedź w 30 sekund: NestJS to progresywny framework Node.js do budowy wydajnych, skalowalnych aplikacji serwerowych. Wykorzystuje TypeScript, architekturę modularną inspirowaną Angular i wzorce jak Dependency Injection. Rozwiązuje główny problem Express.js - brak narzuconej struktury i architektury.
Odpowiedź w 2 minuty: NestJS powstał, aby rozwiązać problem "architektonicznego chaosu" w aplikacjach Node.js. Express.js jest świetny, ale nie narzuca żadnej struktury - każdy projekt wygląda inaczej.
NestJS wprowadza:
- Modularną architekturę - jasny podział na moduły, kontrolery, serwisy
- Dependency Injection - wbudowany kontener IoC
- TypeScript first - pełne wsparcie dla typów
- Dekoratory - czytelna konfiguracja przez @Module, @Controller, @Injectable
- Abstrakcja HTTP - działa z Express lub Fastify pod spodem
// Przykład struktury NestJS
@Module({
imports: [DatabaseModule],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
NestJS jest idealny dla:
- Enterprise applications
- Microservices
- GraphQL APIs
- Real-time applications (WebSockets)
Jaka jest różnica między NestJS a Express.js?
Odpowiedź w 30 sekund: Express.js to minimalistyczny framework bez narzuconej struktury. NestJS to pełnoprawny framework budowany na Express (lub Fastify), który dodaje architekturę modularną, Dependency Injection, TypeScript i wzorce enterprise. Express daje wolność, NestJS daje strukturę.
Odpowiedź w 2 minuty: Najlepiej zobrazować różnice w formie porównania tabelarycznego - poniżej zestawienie kluczowych cech obu frameworków:
| Cecha | Express.js | NestJS |
|---|---|---|
| Architektura | Brak narzuconej | Modularna, opinionated |
| TypeScript | Opcjonalny | Native |
| DI Container | Brak | Wbudowany |
| Struktura projektu | Dowolna | Zdefiniowana |
| Krzywa uczenia | Niska | Średnia |
| Skalowanie | Wymaga planowania | Wbudowane wzorce |
// Express.js
app.get('/users', (req, res) => {
res.json(users);
});
// NestJS
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll();
}
}
NestJS pod spodem używa Express (domyślnie) lub Fastify, więc możesz korzystać z całego ekosystemu middleware Express.
Moduły i Architektura
Czym jest moduł w NestJS i jaką rolę pełni?
Odpowiedź w 30 sekund:
Moduł to podstawowa jednostka organizacyjna w NestJS, oznaczona dekoratorem @Module(). Grupuje powiązane kontrolery i providery, definiuje zależności (imports) i eksporty. Każda aplikacja ma przynajmniej jeden moduł główny (AppModule).
Odpowiedź w 2 minuty: Moduły w NestJS organizują aplikację w spójne bloki funkcjonalne. Każdy moduł enkapsuluje swoją logikę i może być importowany przez inne moduły.
@Module({
imports: [DatabaseModule, AuthModule], // Zależności
controllers: [UsersController], // Kontrolery
providers: [UsersService, UsersRepository], // Serwisy
exports: [UsersService], // Udostępnione na zewnątrz
})
export class UsersModule {}
Właściwości @Module():
-
imports- moduły, których providerów potrzebujemy -
controllers- kontrolery obsługujące żądania HTTP -
providers- serwisy, repozytoria, factory, itp. -
exports- providery dostępne dla innych modułów
Struktura aplikacji:
src/
├── app.module.ts # Root module
├── users/
│ ├── users.module.ts
│ ├── users.controller.ts
│ └── users.service.ts
├── auth/
│ ├── auth.module.ts
│ └── auth.service.ts
Czym są moduły dynamiczne i kiedy ich używać?
Odpowiedź w 30 sekund:
Moduły dynamiczne to moduły konfigurowane w runtime przez metody statyczne (np. forRoot(), forRootAsync()). Używane gdy moduł wymaga konfiguracji - np. ConfigModule z różnymi opcjami, DatabaseModule z connection string.
Odpowiedź w 2 minuty:
Moduły dynamiczne pozwalają na elastyczną konfigurację w czasie uruchomienia aplikacji. Poniżej przykład implementacji modułu z metodami forRoot() i forRootAsync():
// Definicja modułu dynamicznego
@Module({})
export class DatabaseModule {
static forRoot(options: DatabaseOptions): DynamicModule {
return {
module: DatabaseModule,
providers: [
{
provide: 'DATABASE_OPTIONS',
useValue: options,
},
DatabaseService,
],
exports: [DatabaseService],
};
}
static forRootAsync(options: AsyncDatabaseOptions): DynamicModule {
return {
module: DatabaseModule,
imports: options.imports || [],
providers: [
{
provide: 'DATABASE_OPTIONS',
useFactory: options.useFactory,
inject: options.inject || [],
},
DatabaseService,
],
exports: [DatabaseService],
};
}
}
// Użycie
@Module({
imports: [
DatabaseModule.forRoot({
host: 'localhost',
port: 5432,
}),
// lub async
DatabaseModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
host: config.get('DB_HOST'),
port: config.get('DB_PORT'),
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}
Konwencje nazewnictwa:
-
forRoot()- konfiguracja globalna (raz w AppModule) -
forFeature()- konfiguracja per-moduł -
forRootAsync()- async konfiguracja z DI
Kontrolery i Routing
Czym jest kontroler w NestJS i za co odpowiada?
Odpowiedź w 30 sekund:
Kontroler to klasa oznaczona @Controller(), która obsługuje przychodzące żądania HTTP i zwraca odpowiedzi. Odpowiada za routing - mapowanie URL i metod HTTP na odpowiednie handlery. Nie powinien zawierać logiki biznesowej - deleguje ją do serwisów.
Odpowiedź w 2 minuty: Kontroler jest warstwą odpowiedzialną za obsługę żądań HTTP i delegowanie logiki do serwisów. Poniżej pełny przykład kontrolera z podstawowymi operacjami CRUD:
@Controller('users') // Prefix: /users
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get() // GET /users
findAll() {
return this.usersService.findAll();
}
@Get(':id') // GET /users/:id
findOne(@Param('id') id: string) {
return this.usersService.findOne(+id);
}
@Post() // POST /users
@HttpCode(201)
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Put(':id') // PUT /users/:id
update(
@Param('id') id: string,
@Body() updateUserDto: UpdateUserDto,
) {
return this.usersService.update(+id, updateUserDto);
}
@Delete(':id') // DELETE /users/:id
remove(@Param('id') id: string) {
return this.usersService.remove(+id);
}
}
Dekoratory parametrów:
-
@Param()- parametry ścieżki -
@Query()- query parameters -
@Body()- body żądania -
@Headers()- nagłówki -
@Req(),@Res()- obiekty request/response
Providery i Dependency Injection
Jak działa Dependency Injection w NestJS?
Odpowiedź w 30 sekund:
NestJS ma wbudowany kontener IoC (Inversion of Control), który automatycznie zarządza zależnościami. Klasy oznaczone @Injectable() są rejestrowane jako providery i mogą być wstrzykiwane przez konstruktor. Kontener tworzy instancje i zarządza ich cyklem życia.
Odpowiedź w 2 minuty: System Dependency Injection w NestJS automatyzuje zarządzanie zależnościami między klasami. Poniższy przykład pokazuje kompletny przepływ od definicji serwisu po wstrzyknięcie:
// 1. Oznacz klasę jako injectable
@Injectable()
export class UsersService {
constructor(
private readonly usersRepository: UsersRepository,
private readonly emailService: EmailService,
) {}
async createUser(dto: CreateUserDto) {
const user = await this.usersRepository.create(dto);
await this.emailService.sendWelcome(user.email);
return user;
}
}
// 2. Zarejestruj w module
@Module({
providers: [UsersService, UsersRepository, EmailService],
controllers: [UsersController],
})
export class UsersModule {}
// 3. Wstrzyknij przez konstruktor
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
}
Typy providerów:
@Module({
providers: [
// Standard - klasa
UsersService,
// useClass - inna implementacja
{ provide: UsersService, useClass: MockUsersService },
// useValue - stała wartość
{ provide: 'API_KEY', useValue: 'secret-key' },
// useFactory - dynamiczne tworzenie
{
provide: 'DATABASE_CONNECTION',
useFactory: async (config: ConfigService) => {
return createConnection(config.get('DATABASE_URL'));
},
inject: [ConfigService],
},
],
})
Czym jest scope providera i jakie są dostępne opcje?
Odpowiedź w 30 sekund:
Scope określa cykl życia providera. DEFAULT (singleton) - jedna instancja dla całej aplikacji. REQUEST - nowa instancja per żądanie HTTP. TRANSIENT - nowa instancja przy każdym wstrzyknięciu. Domyślnie wszystko jest singleton.
Odpowiedź w 2 minuty: NestJS oferuje trzy typy scope'ów dla providerów, każdy z innym cyklem życia. Oto przykłady poszczególnych wariantów:
// DEFAULT (singleton) - domyślny
@Injectable()
export class UsersService {}
// REQUEST scope - per żądanie
@Injectable({ scope: Scope.REQUEST })
export class RequestLoggerService {
constructor(@Inject(REQUEST) private request: Request) {}
}
// TRANSIENT scope - nowa instancja zawsze
@Injectable({ scope: Scope.TRANSIENT })
export class HelperService {}
Kiedy używać:
| Scope | Użycie | Wydajność |
|---|---|---|
| DEFAULT | Stateless serwisy, większość przypadków | Najlepsza |
| REQUEST | Gdy potrzebujesz dostępu do request | Średnia |
| TRANSIENT | Gdy każdy consumer potrzebuje własnej instancji | Najgorsza |
Uwaga: REQUEST i TRANSIENT propagują się w górę drzewa zależności - jeśli serwis REQUEST jest wstrzykiwany do singleton, ten singleton też stanie się REQUEST-scoped.
Middleware, Guards, Interceptory, Pipes
Czym są Guards w NestJS i do czego służą?
Odpowiedź w 30 sekund:
Guards to klasy implementujące CanActivate, które decydują czy żądanie powinno być obsłużone. Zwracają true (dostęp) lub false/wyjątek (brak dostępu). Używane głównie do autoryzacji i uwierzytelniania. Wykonywane po middleware, przed interceptorami.
Odpowiedź w 2 minuty: Guards implementują logikę kontroli dostępu w aplikacji. Poniżej przykłady dwóch najczęściej używanych guardów - do uwierzytelniania i autoryzacji:
// Auth Guard
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization?.split(' ')[1];
if (!token) {
throw new UnauthorizedException('Brak tokenu');
}
try {
const payload = this.jwtService.verify(token);
request.user = payload;
return true;
} catch {
throw new UnauthorizedException('Nieprawidłowy token');
}
}
}
// Roles Guard z metadanymi
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<string[]>(
'roles',
context.getHandler(),
);
if (!requiredRoles) return true;
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some(role => user.roles?.includes(role));
}
}
// Użycie
@Controller('admin')
@UseGuards(AuthGuard, RolesGuard)
export class AdminController {
@Get()
@Roles('admin') // Custom decorator
getAdminData() {
return { secret: 'admin data' };
}
}
Czym są Interceptory w NestJS i jakie mają zastosowania?
Odpowiedź w 30 sekund:
Interceptory to klasy implementujące NestInterceptor, które mogą transformować request przed handlerem i response po handlerze. Używane do: logowania, cache'owania, transformacji odpowiedzi, timeout, obsługi błędów. Działają jak "opakowanie" wokół handlera.
Odpowiedź w 2 minuty: Interceptory wykorzystują RxJS do transformacji strumieni danych. Oto trzy praktyczne przykłady zastosowania interceptorów w aplikacji:
// Logging Interceptor
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const now = Date.now();
console.log(`[${request.method}] ${request.url} - Start`);
return next.handle().pipe(
tap(() => {
console.log(`[${request.method}] ${request.url} - ${Date.now() - now}ms`);
}),
);
}
}
// Transform Response Interceptor
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next.handle().pipe(
map(data => ({
success: true,
data,
timestamp: new Date().toISOString(),
})),
);
}
}
// Timeout Interceptor
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
timeout(5000),
catchError(err => {
if (err instanceof TimeoutError) {
throw new RequestTimeoutException();
}
throw err;
}),
);
}
}
Czym są Pipes w NestJS i do czego służą?
Odpowiedź w 30 sekund:
Pipes to klasy implementujące PipeTransform, służące do transformacji i walidacji danych wejściowych. Wbudowane: ValidationPipe, ParseIntPipe, ParseBoolPipe. Wykonywane przed handlerem, mogą rzucać wyjątki jeśli dane są nieprawidłowe.
Odpowiedź w 2 minuty: Pipes w NestJS zapewniają walidację i transformację danych wejściowych przed ich przetworzeniem przez handlery. Poniżej przykłady użycia wbudowanych pipe'ów oraz tworzenia własnych:
// Wbudowane Pipes
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
// id jest już liczbą, nie stringiem
return this.usersService.findOne(id);
}
// ValidationPipe z class-validator
// dto/create-user.dto.ts
export class CreateUserDto {
@IsString()
@MinLength(2)
name: string;
@IsEmail()
email: string;
@IsInt()
@Min(18)
age: number;
}
// Użycie
@Post()
create(@Body(ValidationPipe) createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
// Globalny ValidationPipe (main.ts)
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // Usuń nieznane właściwości
forbidNonWhitelisted: true, // Błąd przy nieznanych
transform: true, // Auto-transformacja typów
}));
// Custom Pipe
@Injectable()
export class ParseDatePipe implements PipeTransform<string, Date> {
transform(value: string): Date {
const date = new Date(value);
if (isNaN(date.getTime())) {
throw new BadRequestException('Invalid date format');
}
return date;
}
}
Jaka jest kolejność wykonywania middleware, guards, interceptors i pipes?
Odpowiedź w 30 sekund: Kolejność: Middleware → Guards → Interceptors (before) → Pipes → Handler → Interceptors (after) → Exception Filters. Middleware nie ma dostępu do kontekstu wykonania, guards decydują o dostępie, pipes walidują/transformują dane.
Odpowiedź w 2 minuty: Zrozumienie kolejności wykonywania poszczególnych komponentów jest kluczowe dla debugowania i projektowania aplikacji. Poniższy diagram przedstawia pełny cykl przetwarzania żądania:
Request
↓
┌─────────────────┐
│ Middleware │ → Klasyczne middleware, brak wiedzy o handlerze
└─────────────────┘
↓
┌─────────────────┐
│ Guards │ → Autoryzacja, dostęp do ExecutionContext
└─────────────────┘
↓
┌─────────────────┐
│ Interceptors │ → Pre-processing (przed handlerem)
│ (before) │
└─────────────────┘
↓
┌─────────────────┐
│ Pipes │ → Walidacja i transformacja parametrów
└─────────────────┘
↓
┌─────────────────┐
│ Handler │ → Metoda kontrolera
└─────────────────┘
↓
┌─────────────────┐
│ Interceptors │ → Post-processing (po handlerze)
│ (after) │
└─────────────────┘
↓
┌─────────────────┐
│Exception Filters│ → Obsługa wyjątków (jeśli wystąpiły)
└─────────────────┘
↓
Response
Jeśli Guard zwróci false lub Pipe rzuci wyjątek, dalsze wykonanie jest przerywane i kontrola trafia do Exception Filters.
Obsługa błędów
Jak tworzyć własne Exception Filters w NestJS?
Odpowiedź w 30 sekund:
Exception Filter to klasa z @Catch() implementująca ExceptionFilter. Przechwytuje wyjątki i formatuje odpowiedź błędu. Można łapać konkretne typy wyjątków lub wszystkie (@Catch()). Stosowane globalnie, per kontroler lub per handler.
Odpowiedź w 2 minuty: Exception Filters pozwalają na centralizację obsługi błędów i formatowanie odpowiedzi. Poniżej przykłady filtrów do obsługi różnych typów wyjątków:
// Custom Exception Filter
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
const exceptionResponse = exception.getResponse();
response.status(status).json({
success: false,
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: typeof exceptionResponse === 'string'
? exceptionResponse
: (exceptionResponse as any).message,
});
}
}
// Catch all exceptions
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const status = exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
response.status(status).json({
success: false,
statusCode: status,
message: 'Internal server error',
});
}
}
// Użycie
@UseFilters(HttpExceptionFilter)
@Controller('users')
export class UsersController {}
// Globalnie (main.ts)
app.useGlobalFilters(new AllExceptionsFilter());
Bazy danych
Jak zintegrować TypeORM z NestJS?
Odpowiedź w 30 sekund:
Używając @nestjs/typeorm. Konfiguracja przez TypeOrmModule.forRoot() w AppModule. Encje definiuje się dekoratorami TypeORM. Repozytoria wstrzykuje się przez @InjectRepository(). Dostępny też wzorzec Repository z metodami CRUD.
Odpowiedź w 2 minuty: Integracja TypeORM z NestJS jest prosta dzięki dedykowanemu modułowi. Poniżej kompletny przykład konfiguracji, definicji encji i użycia w serwisie:
// app.module.ts
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'user',
password: 'password',
database: 'mydb',
entities: [User],
synchronize: true, // Tylko dev!
}),
UsersModule,
],
})
export class AppModule {}
// user.entity.ts
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column({ unique: true })
email: string;
@CreateDateColumn()
createdAt: Date;
}
// users.module.ts
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
// users.service.ts
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
findAll(): Promise<User[]> {
return this.usersRepository.find();
}
findOne(id: number): Promise<User> {
return this.usersRepository.findOneBy({ id });
}
create(createUserDto: CreateUserDto): Promise<User> {
const user = this.usersRepository.create(createUserDto);
return this.usersRepository.save(user);
}
}
Testowanie
Jak testować jednostkowo serwisy w NestJS?
Odpowiedź w 30 sekund:
Używając @nestjs/testing i Test.createTestingModule(). Tworzymy izolowany moduł z mockami zależności. Dla repozytoriów używamy getRepositoryToken(). Jest jako test runner. Mockujemy zewnętrzne zależności przez useValue lub useFactory.
Odpowiedź w 2 minuty:
Testowanie w NestJS wykorzystuje moduł @nestjs/testing do tworzenia izolowanych kontekstów testowych. Poniżej kompletny przykład testowania serwisu z mockami zależności:
// users.service.spec.ts
describe('UsersService', () => {
let service: UsersService;
let repository: Repository<User>;
const mockRepository = {
find: jest.fn(),
findOneBy: jest.fn(),
create: jest.fn(),
save: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(User),
useValue: mockRepository,
},
],
}).compile();
service = module.get<UsersService>(UsersService);
repository = module.get<Repository<User>>(getRepositoryToken(User));
});
afterEach(() => {
jest.clearAllMocks();
});
describe('findAll', () => {
it('should return array of users', async () => {
const users = [{ id: 1, name: 'Jan', email: 'jan@example.com' }];
mockRepository.find.mockResolvedValue(users);
const result = await service.findAll();
expect(result).toEqual(users);
expect(mockRepository.find).toHaveBeenCalled();
});
});
describe('create', () => {
it('should create and return user', async () => {
const dto = { name: 'Jan', email: 'jan@example.com' };
const user = { id: 1, ...dto };
mockRepository.create.mockReturnValue(user);
mockRepository.save.mockResolvedValue(user);
const result = await service.create(dto);
expect(result).toEqual(user);
expect(mockRepository.create).toHaveBeenCalledWith(dto);
expect(mockRepository.save).toHaveBeenCalledWith(user);
});
});
});
Zobacz też
- Kompletny Przewodnik - Rozmowa Node.js Backend Developer - pełny przewodnik przygotowania do rozmowy Node.js
- Express.js Pytania Rekrutacyjne - pytania z Express.js
- Jak Przygotować się do Rozmowy Node.js - praktyczne porady
- Wzorce i Architektura Backend - wzorce projektowe dla backendu
Ten artykuł jest częścią serii przygotowującej do rozmów rekrutacyjnych na stanowisko Node.js Backend Developer. Sprawdź nasze fiszki NestJS z 42 pytaniami i odpowiedziami do nauki.
Chcesz więcej pytań rekrutacyjnych?
To tylko jeden temat z naszego kompletnego przewodnika po rozmowach rekrutacyjnych. Uzyskaj dostęp do 800+ pytań z 13 technologii.
