techlur logo techlur logo
BackendFrontendDatabaseAI & LLMsGeneral & HR Get free consultation

Backend

HTTP fundamentals, Node.js, NestJS, auth, and APIs.

20 questions
Q. What are the common HTTP methods and what is each used for? easy

HTTP methods (verbs) describe the action a client wants to perform on a resource.

  • GET — retrieve data; should never modify state (safe & idempotent).
  • POST — create a new resource; not idempotent (calling twice creates two records).
  • PUT — replace a resource entirely; idempotent.
  • PATCH — partially update a resource.
  • DELETE — remove a resource; idempotent.
  • OPTIONS / HEAD — metadata; OPTIONS is used in CORS preflight, HEAD returns headers only.

Follow-up: “What does idempotent mean?” → Making the same request multiple times has the same effect as making it once.

#http#rest
Q. Map CRUD operations to HTTP methods. easy

CRUD is the set of four basic operations every persistent application needs. Each maps to an HTTP method:

CRUD OperationHTTP MethodExample
CreatePOSTPOST /users — create a new user
ReadGETGET /users/1 — fetch user with id 1
UpdatePUT / PATCHPUT /users/1 — replace user 1
DeleteDELETEDELETE /users/1 — remove user 1

PUT vs PATCH:

  • PUT replaces the entire record. If you omit a field, it may be set to null.
  • PATCH updates only the fields you send; everything else stays the same.

Tip: In interviews, mentioning the PUT vs PATCH distinction shows you understand REST semantics beyond the basics.

#http#rest#crud
Q. Describe the structure of an HTTP request and response. easy

Both HTTP requests and responses share a similar three-part structure: a start line, headers, and an optional body.

HTTP Request:

POST /login HTTP/1.1
Host: api.example.com
Content-Type: application/json

{ "email": "user@example.com", "password": "s3cret" }
  • Start line — method (POST), path (/login), and HTTP version.
  • Headers — key-value metadata like Content-Type, Authorization, Accept.
  • Body — the payload (JSON, form data, etc.). GET requests typically have no body.

HTTP Response:

HTTP/1.1 200 OK
Content-Type: application/json

{ "token": "eyJhbGciOi..." }
  • Status line — HTTP version, status code (200), and reason phrase (OK).
  • HeadersContent-Type, Set-Cookie, Cache-Control, etc.
  • Body — the response payload (HTML, JSON, binary, etc.).

Remember: Headers are always plain text key-value pairs. The body is separated from the headers by a blank line.

#http
Q. How can you transform a request or response before it reaches your handler? medium

Most backend frameworks give you hooks to modify requests and responses at different stages. In NestJS, the three main tools are:

  • Middleware — runs first; has access to req, res, next(). Great for logging, body parsing, and setting headers.
  • Pipes — transform or validate the incoming data before it reaches the handler. For example, ParseIntPipe converts a string param to a number.
  • Interceptors — wrap the handler call, so they can transform both the request and the response. They use RxJS observables.

NestJS Interceptor example — adding a timestamp to every response:

@Injectable()
export class TimestampInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map(data => ({
        ...data,
        timestamp: new Date().toISOString(),
      })),
    );
  }
}

Apply it globally or per-controller:

@UseInterceptors(TimestampInterceptor)
@Controller('users')
export class UsersController { ... }

Key point: The NestJS request lifecycle order is: Middleware → Guards → Interceptors (before) → Pipes → Handler → Interceptors (after) → Exception Filters.

#nestjs#middleware#interceptors
Q. What are HTTP status codes? Give the common ranges and examples. easy

HTTP status codes are three-digit numbers the server returns to tell the client what happened. They are grouped by the first digit:

  • 1xx — Informational: 100 Continue, 101 Switching Protocols. Rarely used directly.
  • 2xx — Success:
    • 200 OK — standard success.
    • 201 Created — a new resource was created (common after POST).
    • 204 No Content — success but nothing to return (common after DELETE).
  • 3xx — Redirection:
    • 301 Moved Permanently — resource has a new URL.
    • 304 Not Modified — cached version is still valid.
  • 4xx — Client Error:
    • 400 Bad Request — malformed or invalid input.
    • 401 Unauthorized — authentication required or failed.
    • 403 Forbidden — authenticated but not allowed.
    • 404 Not Found — resource does not exist.
    • 422 Unprocessable Entity — validation error.
  • 5xx — Server Error:
    • 500 Internal Server Error — generic server failure.
    • 502 Bad Gateway — upstream server returned an invalid response.
    • 503 Service Unavailable — server is overloaded or under maintenance.

401 vs 403: 401 means “I don’t know who you are” (missing or invalid credentials). 403 means “I know who you are, but you’re not allowed to do this.” Getting this distinction right matters in interviews.

#http#rest
Q. How do you create and maintain a session on the backend? medium

There are two main approaches to maintaining user sessions: session-based (server-side) and token-based (client-side).

Session-Based Authentication:

  1. User logs in with credentials.
  2. Server creates a session object and stores it (in memory, Redis, or a database).
  3. Server sends back a session ID in a Set-Cookie header.
  4. Browser automatically sends the cookie on every subsequent request.
  5. Server looks up the session ID to identify the user.

Token-Based Authentication (JWT):

  1. User logs in with credentials.
  2. Server creates and signs a JWT containing user claims.
  3. Client stores the token (localStorage or httpOnly cookie) and sends it via Authorization: Bearer <token>.
  4. Server verifies the token signature — no server-side lookup needed.
Session CookiesJWT
StateServer-side (stateful)Client-side (stateless)
StorageSession store (Redis, DB)Token stored by client
ScalabilityNeeds shared store across serversScales easily — no shared state
RevocationEasy — delete the sessionHard — token valid until it expires
SizeSmall cookie (just an ID)Larger (carries payload)

Best practice: For most web apps, use httpOnly, Secure, SameSite cookies for storing session identifiers or tokens. This mitigates XSS and CSRF risks compared to localStorage.

#auth#sessions#jwt
Q. What is a JWT and how is it used? medium

A JSON Web Token is a compact, signed token with three dot-separated parts: Header.Payload.Signature.

  • Header — algorithm & token type.
  • Payload — claims like userId, role, exp (expiry). This is Base64-encoded, not encrypted — never put secrets here.
  • Signature — created with a secret key; lets the server verify the token wasn’t tampered with.

Flow: user logs in → server signs a JWT → client stores it and sends Authorization: Bearer <token> → server verifies the signature on each request.

Best practice: Use a short-lived access token + a long-lived refresh token. Store tokens in httpOnly cookies when possible to reduce XSS risk.

#auth#security#jwt
Q. What is middleware, and what is a guard? medium

Middleware is a function that runs in the request pipeline before the route handler, with access to req, res, and next(). Used for logging, body parsing, CORS, auth checks.

function logger(req, res, next) {
  console.log(req.method, req.url);
  next(); // pass control onward
}

Guards (NestJS) decide whether a request is allowed to proceed — they return true/false. They run after middleware and are the right place for authentication and role-based authorization.

@Injectable()
class AuthGuard {
  canActivate(ctx) {
    const req = ctx.switchToHttp().getRequest();
    return Boolean(req.headers.authorization);
  }
}
#nestjs#express#middleware
Q. How do you handle errors and error responses in a backend API? medium

Good error handling keeps your API predictable and your users informed without leaking internals.

Key principles:

  • Use a structured error shape — every error response should have a consistent format so clients can parse it reliably.
  • Use typed/named exceptions — throw specific errors (NotFoundException, ValidationError) rather than generic ones.
  • Never leak stack traces — in production, stack traces expose file paths and internal logic. Return a clean message and log the details server-side.
  • Use the right status code — 400 for bad input, 401/403 for auth issues, 404 for missing resources, 500 for unexpected failures.

Express error-handling middleware:

app.use((err, req, res, next) => {
  console.error(err.stack); // log full error server-side

  res.status(err.status || 500).json({
    error: {
      message: err.message || 'Internal Server Error',
      code: err.code || 'UNKNOWN_ERROR',
    },
  });
});

NestJS exception filters:

NestJS has a built-in exception layer. You can throw typed exceptions and they are automatically mapped to responses:

throw new NotFoundException('User not found');
// → { statusCode: 404, message: "User not found" }

For custom shapes, create an exception filter:

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const status = exception.getStatus();

    response.status(status).json({
      success: false,
      error: exception.message,
      timestamp: new Date().toISOString(),
    });
  }
}

Tip: Always validate input at the edge (with DTOs/pipes) so you can return a 400 before the error reaches your business logic.

#nestjs#express#errors
Q. What is CORS and why does it block requests? medium

CORS (Cross-Origin Resource Sharing) is a browser security mechanism that restricts web pages from making requests to a different origin (domain, protocol, or port) than the one that served the page.

Why does it exist?

Without CORS, a malicious site could make requests to your bank’s API using your cookies — the browser would send them automatically. CORS prevents this by requiring the server to explicitly allow cross-origin access.

How it works:

  1. The browser sends a request from http://frontend.com to http://api.example.com.
  2. If the origins differ, the browser checks for CORS headers in the response.
  3. For “non-simple” requests (PUT, DELETE, custom headers, JSON content-type), the browser first sends an OPTIONS preflight request asking “is this allowed?”
  4. The server responds with headers like Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers.
  5. If the headers permit it, the browser proceeds with the actual request. Otherwise, it blocks it.

Key CORS headers:

  • Access-Control-Allow-Origin — which origins are allowed (* for any, or a specific origin).
  • Access-Control-Allow-Methods — which HTTP methods are permitted.
  • Access-Control-Allow-Headers — which custom headers the client can send.
  • Access-Control-Allow-Credentials — whether cookies/auth headers are allowed (cannot use * for origin when this is true).

Fix on the server (Express example):

const cors = require('cors');
app.use(cors({
  origin: 'https://frontend.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true,
}));

Remember: CORS is enforced by the browser, not the server. Server-to-server requests (e.g., from your backend to another API) are never blocked by CORS. If you see a CORS error, the fix is always on the server, not the client.

#cors#security#http
Q. Is Node.js single-threaded? Explain the event loop. medium

Yes and no. Node.js runs your JavaScript on a single thread, but it delegates I/O operations (file reads, network calls, DNS lookups) to libuv, which maintains a thread pool (default 4 threads).

The Event Loop is the mechanism that coordinates this:

  1. Timers — execute callbacks from setTimeout and setInterval.
  2. Pending callbacks — I/O callbacks deferred from the previous cycle.
  3. Poll — retrieve new I/O events; execute I/O-related callbacks. This is where Node spends most of its time.
  4. Check — execute setImmediate callbacks.
  5. Close callbacks — e.g., socket.on('close', ...).

Between each phase, Node processes the microtask queue (resolved Promises, process.nextTick).

Why does this matter?

  • I/O is non-blocking — while one request waits for a database response, Node can handle other requests.
  • CPU-heavy work blocks the loop — a big for loop or image processing will freeze all other requests because there is only one JS thread.

How to handle CPU-heavy tasks:

  • Use Worker Threads (worker_threads module) to run code on a separate thread.
  • Offload to a child process (child_process.fork).
  • Use a job queue (Bull, BeeQueue) for background processing.

Key takeaway: Node.js is single-threaded for JavaScript execution but uses multiple threads under the hood for I/O. The event loop is what makes non-blocking async work possible.

#nodejs#event-loop
Q. Callbacks vs Promises vs async/await — what's the difference? easy

All three are ways to handle asynchronous operations in JavaScript. Each is an evolution of the previous.

Callbacks — a function passed as an argument, called when the async work completes. Works, but nesting multiple callbacks creates callback hell (deeply indented, hard-to-read code).

fs.readFile('a.txt', (err, data) => {
  if (err) throw err;
  fs.readFile('b.txt', (err, data2) => {
    if (err) throw err;
    console.log(data, data2); // nested and messy
  });
});

Promises — an object representing a future value. Chain .then() for success and .catch() for errors. Flattens the nesting.

readFilePromise('a.txt')
  .then(data => readFilePromise('b.txt'))
  .then(data2 => console.log(data2))
  .catch(err => console.error(err));

async/await — syntactic sugar over Promises. Makes async code look and behave like synchronous code. Uses try/catch for error handling.

async function readFiles() {
  try {
    const data = await readFilePromise('a.txt');
    const data2 = await readFilePromise('b.txt');
    console.log(data, data2);
  } catch (err) {
    console.error(err);
  }
}

Tip: async/await is still using Promises under the hood — it’s not a different mechanism. Every async function returns a Promise, and await pauses execution until that Promise resolves.

#javascript#async
Q. What is the difference between process.nextTick, microtasks, and setTimeout? hard

These three schedule callbacks at different points in the Node.js event loop, and their execution order is a common interview question.

Execution priority (highest to lowest):

  1. process.nextTick — fires immediately after the current operation, before any other I/O or timer. It is processed from the nextTick queue, which is drained completely before moving on.
  2. Microtasks (Promise .then, queueMicrotask) — processed from the microtask queue, right after the nextTick queue is empty. These run between every phase of the event loop.
  3. setTimeout(fn, 0) / setImmediate — these are macrotasks. setTimeout runs in the Timers phase; setImmediate runs in the Check phase.

Example:

console.log('1 - start');

setTimeout(() => console.log('2 - setTimeout'), 0);

Promise.resolve().then(() => console.log('3 - Promise'));

process.nextTick(() => console.log('4 - nextTick'));

console.log('5 - end');

Output:

1 - start
5 - end
4 - nextTick
3 - Promise
2 - setTimeout

Synchronous code runs first (1, 5), then process.nextTick (4), then the microtask/Promise (3), and finally the macrotask/setTimeout (2).

Warning: Overusing process.nextTick can starve I/O because the nextTick queue is drained completely before the event loop continues. Prefer setImmediate or queueMicrotask unless you specifically need nextTick’s priority.

#nodejs#event-loop
Q. What does module.exports vs require do, and how does ESM differ? easy

Node.js has two module systems: CommonJS (CJS) and ES Modules (ESM).

CommonJS (the original Node.js way):

  • module.exports — defines what a file exports.
  • require() — imports a module synchronously.
// math.js
module.exports = { add: (a, b) => a + b };

// app.js
const math = require('./math');
console.log(math.add(2, 3)); // 5

ES Modules (the modern standard):

  • export / export default — defines exports.
  • import — imports a module. Statically analyzed and asynchronous.
// math.mjs
export const add = (a, b) => a + b;

// app.mjs
import { add } from './math.mjs';
console.log(add(2, 3)); // 5

Key differences:

CommonJSES Modules
Syntaxrequire() / module.exportsimport / export
LoadingSynchronousAsynchronous
ParsingDynamic (can require conditionally)Static (imports hoisted to top)
Tree-shakingNot possibleSupported (bundlers remove unused exports)
File extension.js (default).mjs or .js with "type": "module" in package.json

Tip: To use ESM in Node.js, either name your files .mjs or add "type": "module" to your package.json. Most modern frameworks (including NestJS and Astro) use ESM by default.

#nodejs#modules
Q. Explain the core building blocks of NestJS. medium

NestJS organizes applications around a set of core building blocks, each with a specific responsibility:

  • Module (@Module) — a class that groups related controllers and providers. Every app has at least a root AppModule. Modules can import other modules.
  • Controller (@Controller) — handles incoming HTTP requests and returns responses. Defines routes with decorators like @Get(), @Post(), etc.
  • Provider / Service (@Injectable) — contains business logic. Services are injected into controllers via dependency injection.
  • Pipe — transforms or validates input data before it reaches the handler. Example: ValidationPipe validates DTOs, ParseIntPipe converts strings to numbers.
  • Guard (@Injectable, implements CanActivate) — determines whether a request is allowed to proceed. Used for authentication and authorization.
  • Interceptor (@Injectable, implements NestInterceptor) — wraps the handler execution. Can transform the response, add logging, or cache results.
  • Exception Filter (@Catch) — catches thrown exceptions and converts them into HTTP responses.

Request lifecycle order:

Incoming Request
  → Middleware
    → Guards
      → Interceptors (before)
        → Pipes
          → Route Handler
        → Interceptors (after)
      → Exception Filters (if error)
  → Response

Key insight: Each building block has a single responsibility. Guards only decide yes/no, Pipes only validate/transform data, Interceptors wrap the handler. This separation makes NestJS apps testable and maintainable.

#nestjs#architecture
Q. What is Dependency Injection and why does NestJS use it? medium

Dependency Injection (DI) is a design pattern where a class receives its dependencies from the outside rather than creating them itself. An IoC (Inversion of Control) container manages the creation and lifetime of these dependencies.

Without DI (tightly coupled):

class UserController {
  private userService = new UserService(); // creates its own dependency
}

With DI (loosely coupled):

@Controller('users')
class UserController {
  constructor(private readonly userService: UserService) {}
  // NestJS injects UserService automatically
}

Why NestJS uses DI:

  • Loose coupling — classes depend on abstractions, not concrete implementations. You can swap a DatabaseService for a MockDatabaseService without changing the controller.
  • Testability — in unit tests, you inject mock or stub services instead of real ones.
  • Single responsibility — each service handles one concern; the container wires them together.
  • Lifecycle management — the container controls whether a service is a singleton (default in NestJS), request-scoped, or transient.

How it works in NestJS:

  1. Mark a class as injectable with @Injectable().
  2. Register it in a module’s providers array.
  3. Declare it as a constructor parameter wherever you need it.
@Injectable()
export class UserService {
  findAll() { return ['Alice', 'Bob']; }
}

@Module({
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

Tip: If an interviewer asks “what is IoC?” — it is the principle that the framework controls object creation and lifecycle, not your code. DI is the most common way to implement IoC.

#nestjs#di#architecture
Q. What is a DTO and why use class-validator? easy

A DTO (Data Transfer Object) is a plain class that defines the shape of data coming into or going out of your API. It does not contain business logic — it is purely a data contract.

Why use DTOs?

  • Validation — ensure incoming data matches the expected shape before it reaches your service.
  • Documentation — the DTO clearly shows what fields an endpoint expects.
  • Type safety — TypeScript catches mismatches at compile time.

class-validator is a library that uses decorators to define validation rules on DTO properties. Paired with NestJS’s ValidationPipe, invalid requests are automatically rejected with a 400 error.

import { IsEmail, IsString, MinLength } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @MinLength(2)
  name: string;

  @IsEmail()
  email: string;

  @IsString()
  @MinLength(8)
  password: string;
}

Using it in a controller:

@Post()
create(@Body() dto: CreateUserDto) {
  return this.userService.create(dto);
}

Enable ValidationPipe globally in main.ts:

app.useGlobalPipes(new ValidationPipe({
  whitelist: true,       // strip unknown properties
  forbidNonWhitelisted: true, // throw if unknown properties are sent
  transform: true,       // auto-transform payloads to DTO instances
}));

Tip: Always enable whitelist: true to strip unexpected fields. This prevents users from injecting extra properties (like isAdmin: true) into your database.

#nestjs#validation#dto
Q. What makes an API RESTful? medium

REST (Representational State Transfer) is an architectural style for designing networked APIs. An API is considered RESTful when it follows these principles:

  • Stateless — every request contains all the information the server needs. The server does not store client session state between requests.
  • Resource-based URLs — endpoints represent resources (nouns), not actions (verbs). Use /users/5 instead of /getUser?id=5.
  • Correct HTTP methods — use GET to read, POST to create, PUT/PATCH to update, DELETE to remove.
  • Proper status codes — return 201 for creation, 404 for not found, 400 for bad input, etc.
  • Uniform interface — consistent URL patterns, predictable request/response formats (usually JSON).
  • Client-server separation — the client and server are independent; the client only knows the API contract.

Good REST design:

GET    /users          → list all users
GET    /users/42       → get user 42
POST   /users          → create a new user
PUT    /users/42       → replace user 42
PATCH  /users/42       → partially update user 42
DELETE /users/42       → delete user 42
GET    /users/42/posts → list posts by user 42

Common mistakes:

  • Using verbs in URLs: /getUsers, /deleteUser/42
  • Using POST for everything
  • Returning 200 for errors with an error message in the body
  • Not using plural nouns for collections (/user vs /users)

Interview tip: REST is a set of guidelines, not a strict specification. In practice, very few APIs are “purely” RESTful — what matters is consistent, predictable conventions that your team agrees on.

#rest#api
Q. REST vs GraphQL — when would you choose each? medium

REST and GraphQL are two approaches to building APIs. Each has trade-offs.

REST:

  • Multiple endpoints, one per resource (/users, /posts, /comments).
  • The server decides what data to return for each endpoint.
  • Simple HTTP caching with Cache-Control, ETag, etc.
  • Can lead to over-fetching (getting more fields than you need) or under-fetching (needing multiple requests to assemble a view).

GraphQL:

  • A single endpoint (typically /graphql).
  • The client sends a query specifying exactly which fields it needs.
  • No over-fetching or under-fetching — the response shape matches the query.
  • Built-in schema and type system for self-documentation.
RESTGraphQL
EndpointsMany (one per resource)Single (/graphql)
Data shapeServer decidesClient decides
CachingEasy (HTTP-level)Harder (needs client-side cache like Apollo)
Learning curveLowerHigher (schema, resolvers, query language)
File uploadsStraightforwardRequires extra setup
Real-timePolling or WebSocketsSubscriptions built in

When to choose REST:

  • Simple CRUD applications with predictable data needs.
  • When HTTP caching is important.
  • When the team is more familiar with REST patterns.

When to choose GraphQL:

  • Complex frontends that need different slices of data on different screens.
  • Mobile apps where minimizing payload size matters.
  • When you have many related resources and want to avoid multiple round trips.

Tip: They are not mutually exclusive. Some teams use REST for simple CRUD and add a GraphQL layer for complex, relationship-heavy queries.

#rest#graphql#api
Q. How do you secure a backend application? (name several) hard

Backend security is not a single feature — it is a layered approach. Here are the essential practices:

Authentication & Passwords:

  • Hash passwords with bcrypt (or Argon2) — never store plain text. bcrypt includes a salt and is intentionally slow to resist brute-force attacks.
  • Use JWT or session cookies for auth. Prefer httpOnly, Secure, SameSite cookies to reduce XSS and CSRF risk.

Input Validation:

  • Validate and sanitize all input — never trust data from the client. Use a validation library (class-validator, Joi, Zod) at the API boundary.
  • Parameterized queries (prepared statements) — prevent SQL injection. Never concatenate user input into SQL strings.
// BAD — SQL injection risk
db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);

// GOOD — parameterized query
db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);

Transport & Headers:

  • HTTPS everywhere — encrypt data in transit. Redirect HTTP to HTTPS.
  • Use helmet (Express middleware) — sets security headers like X-Content-Type-Options, Strict-Transport-Security, X-Frame-Options.
  • CORS configuration — only allow trusted origins.

Rate Limiting & Abuse Prevention:

  • Rate-limit endpoints — prevent brute-force login attempts and DDoS. Use libraries like express-rate-limit or @nestjs/throttler.
  • Limit payload size — prevent large request body attacks.

Other Important Measures:

  • Principle of least privilege — give services and database users only the permissions they need.
  • Keep dependencies updated — run npm audit regularly to catch known vulnerabilities.
  • Never expose stack traces in production — return generic error messages and log details server-side.
  • Environment variables — store secrets (API keys, DB passwords) in environment variables, never in code.

Remember: Security is defense in depth. No single measure is enough. Combining multiple layers — hashing, validation, HTTPS, rate-limiting, least privilege — makes your application resilient against different attack vectors.

#security#auth