18 questions
NestJS interview questions and answers
Middle questions
1. After an incident you find that a NestJS API returned a database driver error, with the password from the connection string, to the client in a 500 response. How could that happen if Nest's default filter hides details, and how do you make it impossible?
Answer
The built-in exception filter really does return only 500 and
Internal server errorfor a plainErrorand writes the text to the log. The leak happens when someone wraps the error by hand:throw new InternalServerErrorException(err.message)ornew HttpException(err, 500)turn the message or the passed object into the response body, or a custom filter returnsexception.messageorstack. Protection: one global filter registered withAPP_FILTERthat returns a generic body with arequestIdfor anything except knownHttpExceptionand domain errors, and writes the full error to the log with secrets redacted. Plus a review or lint rule against passing raw errors intoHttpException, and a test that a 500 body has no details. And the connection string itself must not end up in error texts and logs.Follow-up question: How do you return validation errors with field details without exposing details of other errors?
- Knowledge base: NestJS
- Source: docs.nestjs.com
2. In a NestJS project the JWT and role check was done in Express middleware, and a separate map of paths to roles had to be maintained. Why does Nest have guards for this, and what does a guard see that middleware does not?
Answer
Middleware runs before Nest routing and only knows
reqandres, so it does not know which controller and handler will process the request, and roles have to be duplicated in a separate path map. A guard gets theExecutionContext: throughcontext.getHandler()andgetClass()it reads route metadata, andReflectorpulls the roles from a@Roles('admin')decorator right above the method. The rule lives next to the code and does not drift from the routes after a rename. A guard also works the same way for HTTP, WebSockets and microservices throughswitchToHttp()orswitchToWs(). Middleware stays for things that do not depend on the route: logging,helmet, a cookie parser.Follow-up question: A global guard is registered with
app.useGlobalGuards(new AuthGuard()), and itsReflectorisundefined. Why?- Knowledge base: NestJS
- Source: docs.nestjs.com
3. In NestJS,
UsersServicedepends onMailService, which is declared inprovidersofMailModule, andUsersModulehasimports: [MailModule]. The app does not start withNest can't resolve dependencies. Why, and how do you fix it?Answer
A module in Nest is a visibility boundary: a module's providers are private until they are added to
exports. The entryimports: [MailModule]gives access only to what that module exports, andMailModuleexports nothing. That is whyUnknownDependenciesExceptionappears. The one-line fix is to addexports: [MailService]toMailModule. DuplicatingMailServicein theprovidersofUsersModuleis not advisable, because that creates a second instance.Follow-up question: When is it right to make a module
@Global(), and when does it hide dependencies?- Knowledge base: NestJS
- Source: docs.nestjs.com
4. In a NestJS integration test you need to replace
PaymentsClient, which calls the real payment API, with a fake while keeping the rest of the module real. How do you do it, and when would you useuseValueversususeClassoruseFactory?Answer
Build the module with
Test.createTestingModule({ imports: [PaymentsModule] })and beforecompile()call.overrideProvider(PaymentsClient).useValue(fake). The override works by token, so if the service injects the client with@Inject(PAYMENTS_CLIENT), that token is the one to override.useValuesuits a ready object withjest.fn()ormock.fn(): Nest injects nothing into it.useClassgives a fake class that Nest passes dependencies into, anduseFactorywithinjectis for a fake built from other providers, for example from config.providers: [PaymentsClient]in a module is only shorthand for{ provide: PaymentsClient, useClass: PaymentsClient }, so the substitution mechanism is the same as in production.Follow-up question: How do you make sure the real
PaymentsClientcannot be called by accident in any test?- Knowledge base: NestJS
- Source: docs.nestjs.com
5. In NestJS,
CheckoutServicedepends on the interfacePaymentGateway, andproviderscontains the classLiqPayGatewaythat implements it. Startup fails withNest can't resolve dependencies. Why, and how do you fix it while keeping the dependency on an abstraction?Answer
TypeScript interfaces do not exist at runtime, so Nest has no token: in the constructor metadata an interface is recorded simply as
Object. You need an explicit token: aSymbol, a string or an abstract class. You register{ provide: PAYMENT_GATEWAY, useClass: LiqPayGateway }and write@Inject(PAYMENT_GATEWAY)in the constructor. That way the service depends on an abstraction, and the implementation is easy to swap in tests or by config.Follow-up question: What breaks if you import the service with
import type?- Knowledge base: NestJS, TypeScript
- Source: docs.nestjs.com
6. In what order are lifecycle hooks called in NestJS at startup and at shutdown? Where is it better to open a database connection: in the constructor or in
onModuleInit?Answer
The constructor runs when the app is created, while
onModuleInitandonApplicationBootstrapare called atlistenorinit. Startup order: constructor,onModuleInit,onApplicationBootstrap. Shutdown goes:onModuleDestroy,beforeApplicationShutdown,onApplicationShutdown. An asynconModuleInitdelays startup, because Nest waits for its Promise. That is why connections are opened inonModuleInitand not in the constructor, which cannot be async. System signals work only afterenableShutdownHooks().Follow-up question: Are these hooks called for a request-scoped class?
- Knowledge base: NestJS
- Source: docs.nestjs.com
7. Name the full request processing chain in NestJS in the correct order. Where do exception filters work?
Answer
The order is: middleware, guards, interceptors (the part before the handler), pipes, the handler itself, then interceptors on the way back. A pipe runs after an interceptor, not before it. An interceptor wraps the handler call: code before
next.handle()runs on the way in, code intapormapon the way out. Exception filters fire only when an exception was thrown and not caught somewhere in this chain. Within each type the order is: global, then controller, then route.Follow-up question: Why are exception filters looked up in reverse, from route to global?
- Knowledge base: NestJS
- Source: docs.nestjs.com
8. What do the
whitelist,forbidNonWhitelistedandtransformoptions do in the NestJSValidationPipe? What happens to an extrarolefield in the body and topage=2in the query?Answer
With
whitelist: truethe pipe silently strips fields that have no validation decorators, so the extrarolein the body disappears.forbidNonWhitelisted: truereturns 400 saying the property should not exist instead of stripping it. Withtransform: truethe pipe turns a plain object into an instance of the DTO class, and with@Type(() => Number)the stringpage=2from the query becomes a number. Withoutwhitelistextra fields pass through and create a mass assignment vulnerability.Follow-up question: Why is the value in the DTO not a class instance without
transform?- Knowledge base: NestJS, TypeScript
- Source: docs.nestjs.com
9.
ValidationPipeis enabled, butPOST /orderswithitems: [{ sku: 5, qty: -1 }]returns 201.CreateOrderDtohas only@IsArray(). Why did theItemDtorules not fire, and what should you add?Answer
@IsArray()only checks that the value is an array and does not look inside the elements. TheItemDto[]type in TypeScript disappears at runtime, so the validator does not know what these elements are. You need@ValidateNested({ each: true })to validate every element, and@Type(() => ItemDto)fromclass-transformerso the elements become class instances with metadata. Without@Typethe elements stay plain objects and the decorators on them have no effect.Follow-up question: How do you limit the array size and validate a union in a nested field?
- Knowledge base: NestJS, TypeScript
- Source: docs.nestjs.com
10. How do you organize configuration in NestJS so the app does not start without
DATABASE_URLinstead of failing on the first request?Answer
You add
ConfigModule.forRoot({ isGlobal: true })and pass avalidationSchema(for example Joi) or avalidatefunction: environment variables are checked at startup and the app fails right away with a clear error. In code you useConfigService.getOrThrow, which throws if the value is missing. WithregisterAsthe config is grouped into a namespace. Note thatregisterAsvalidates nothing by itself, the check is still done by the schema orvalidate.Follow-up question: How do you pass the config to
TypeOrmModule.forRootAsyncand change a value in a test?- Knowledge base: NestJS
- Source: docs.nestjs.com
11. Walk a request through authentication with Passport and JWT in NestJS, from login to
req.userin the controller. What must never go into the token?Answer
At login the server compares the password with the hash and signs a JWT via
JwtService. On a protected routeAuthGuard('jwt')runsJwtStrategy: it takes the Bearer token from the header and verifies the signature andexp. Thenvalidate(payload)is called and its result becomesreq.user. A JWT is signed but not encrypted, so its contents can be read without a key, and passwords and sensitive data are never put into it. Authentication answers "who are you", authorization answers "what are you allowed to do".Follow-up question: How do you revoke an already issued access token?
- Course: Auth and security
- Knowledge base: NestJS, JWT
- Source: docs.nestjs.com
12. All endpoints of a NestJS API must require a JWT by default, except
/healthand/auth/login. How do you do this so a new controller cannot be accidentally left open?Answer
You register a global guard through the
APP_GUARDprovider instead of@UseGuardson every controller, so protection applies by default. Exceptions are marked with a custom@Public()decorator that sets a flag viaSetMetadata. The guard reads it withReflector.getAllAndOverrideon the handler and the class. Payment provider webhooks are also public as far as JWT is concerned, but they must verify the request signature. On top of that you write a test that walks all routes and expects 401 without a token, except for an allowlist.Follow-up question: How do you support both JWT for users and API keys for partners in the same guard?
- Course: Auth and security
- Knowledge base: NestJS, JWT
- Source: docs.nestjs.com
13. You are writing an internal NestJS module for a payment provider client. Three services use it with different keys, and the key must come from
ConfigService. How do you make the module configurable?Answer
Make it a dynamic module: a static method returns a
DynamicModulewithmodule, an options provider under its own token ({ provide: PAYMENTS_OPTIONS, useValue: options }), a client that injects those options, andexports. Since the key depends on another provider, you need the async variant:forRootAsync({ imports: [ConfigModule], useFactory: (config) => ({ apiKey: config.getOrThrow('PAY_KEY') }), inject: [ConfigService] }). The names carry meaning:forRootis configured once at the root,registergives a separate instance per import. To avoid writing this by hand there isConfigurableModuleBuilder, which generates typedregisterandregisterAsync.@Global()is not needed here: an explicit import shows who depends on payments.Follow-up question: How do you test a service that uses this module with fake options without bringing up
ConfigModule?- Knowledge base: NestJS
- Source: docs.nestjs.com
14.
OrdersModuleimportsUsersModule, andUsersModulealready importedOrdersModule, and NestJS crashes at startup with an error aboutundefinedinimports. How do you fix it quickly, and how properly?Answer
A cycle between modules or between files (often through a barrel
index.ts) makes the classundefinedat the moment the decorator is applied. The quick fix isforwardRef(() => Module)in both modules and@Inject(forwardRef(...))for providers. But the creation order in a cycle is undefined, so you cannot rely on a dependency in the constructor. It is better to break the cycle: move shared logic into a third module or reverse the dependency via events. Cycles between files are found with a tool likemadge --circular.Follow-up question: Does the caller of
emitlearn about an error in an async EventEmitter2 listener?- Knowledge base: NestJS
- Source: docs.nestjs.com
15. A money transfer in NestJS "half succeeds": the debit happened, the credit failed, but there is no rollback. Inside
dataSource.transaction(...)some queries go through an injected repository. What is the cause?Answer
The injected repository works through a different connection from the pool, so its queries run outside the transaction and rollback does not touch them. All operations inside the transaction must go through the
managergiven by the callback:manager.decrement(...),manager.save(...). Rollback undoes only what went through thatmanager. In Prisma the rule is the same: queries go throughtxin$transaction(async (tx) => ...), not through the global client.Follow-up question: How do you pass the transaction through several services without dragging
managerinto every method?- Course: Prisma ORM
- Knowledge base: NestJS, PostgreSQL, Prisma
- Source: typeorm.io
16. Services throw domain errors
OrderNotFoundandInsufficientFunds, and Prisma throwsP2002on a duplicate. How do you design error handling in NestJS so the client gets correct statuses in a uniform format?Answer
Domain errors do not extend
HttpException, so the domain layer does not depend on HTTP. One global exception filter maps error types to statuses:OrderNotFoundto 404,InsufficientFundsto 422 or 409, PrismaP2002to 409,P2025to 404. The response body has a uniform format, for example Problem Details with arequestId. Unknown errors are returned as 500 with a generic text, and the full stack trace goes only to the log. The filter is registered viaAPP_FILTERso DI works.Follow-up question: How do you handle the same set of domain errors in a queue consumer, where there are no HTTP statuses?
- Knowledge base: NestJS, Prisma
- Source: docs.nestjs.com
17. What scope do NestJS providers have by default, and what does that mean for state in class fields? When is
REQUESTscope needed, what does it cost, and what can you use instead?Answer
By default providers are singletons: one instance per application. So request state in the fields of such a service gets mixed between concurrent requests.
Scope.REQUESTcreates an instance per request but "bubbles up" the dependency chain: everything that depends on it becomes request-scoped too. The cost is extra allocations and slower requests. For request context (id, tenant, user) it is better to useAsyncLocalStorage. A cron job or queue consumer that depends on a request-scoped provider has no HTTP request, and that causes an error.Follow-up question: What happens to a cron job if it depends on a request-scoped provider?
- Knowledge base: NestJS
- Source: docs.nestjs.com
18. A NestJS latency metrics interceptor always logs
took 0ms: it callsnext.handle()and logs the difference betweenDate.now()andstartright away, before the result is returned. What is wrong, how do you rewrite it, and which requests will it not see?Answer
next.handle()returns a lazy Observable: the handler runs only on subscription, that is afterintercepthas already returned. So the time difference is almost zero. You must measure inside the stream:next.handle().pipe(tap(...)), and for errors add error handling orfinalize. The error status is set later by the exception filter, so you take it from the error itself. Guards run before interceptors, so 401 and 403 responses will not reach this metric. For the full picture you add middleware withres.on('finish').Follow-up question: How do you add a 5 second timeout to the handler in an interceptor, and what happens to the work the handler already started?
- Knowledge base: NestJS
- Source: docs.nestjs.com
Train these questions
Study the cards, answer by text or voice and get an AI grade. Needs a signed in account.
Answer out loud and get graded
Here you get the questions with model answers. In the ShortLesson app you answer yourself, by text or voice, and AI grades the answer against the rubric and asks a follow-up, like a real interviewer.
The app is coming to the stores soon.
Other topics
- JavaScript interview questions and answers
- TypeScript interview questions and answers
- React interview questions and answers
- Frontend performance interview questions and answers
- Node.js interview questions and answers
- HTTP interview questions and answers
- SQL interview questions and answers
- Databases interview questions and answers
- Git interview questions and answers
- Algorithms interview questions and answers
- System design interview questions and answers
- DevOps interview questions and answers
- Web security interview questions and answers
- Testing interview questions and answers