Skip to content

Latest commit

 

History

History
1077 lines (758 loc) · 27.4 KB

File metadata and controls

1077 lines (758 loc) · 27.4 KB

Frequently Asked Questions - Node.js & Express.js Backend Development

This document contains common questions and answers that will help you become a proficient Node.js and Express.js backend developer. These are questions you might encounter in interviews or need to understand deeply.


📚 Table of Contents

  1. Node.js Fundamentals
  2. Express.js Concepts
  3. Backend Development Best Practices
  4. API Design & REST
  5. Authentication & Security
  6. Error Handling & Debugging
  7. Performance & Optimization
  8. Database & Data Management
  9. Testing & Deployment
  10. Common Interview Questions

Node.js Fundamentals

Q1: What is Node.js and why is it used for backend development?

Answer: Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. It allows JavaScript to run on the server side, outside of the browser. It's used for backend development because:

  • Single Language: Use JavaScript for both frontend and backend
  • Non-blocking I/O: Asynchronous, event-driven architecture handles many concurrent connections efficiently
  • Large Ecosystem: npm (Node Package Manager) has millions of packages
  • Fast: V8 engine compiles JavaScript to machine code
  • Scalable: Great for handling many simultaneous connections

Q2: What is the event loop in Node.js?

Answer: The event loop is what allows Node.js to perform non-blocking I/O operations. It:

  • Monitors the call stack and callback queue
  • Moves callbacks from the queue to the call stack when the stack is empty
  • Handles asynchronous operations efficiently
  • Uses phases: timers, pending callbacks, idle/prepare, poll, check, close callbacks

Key Concept: Node.js is single-threaded, but the event loop allows it to handle concurrency through asynchronous operations.

Q3: What is the difference between require() and import?

Answer:

  • require() (CommonJS):

    • Synchronous loading
    • Used in Node.js by default
    • Returns module.exports
    • Example: const express = require('express')
  • import (ES6 Modules):

    • Asynchronous loading
    • Must use .mjs extension or "type": "module" in package.json
    • Static analysis (can't be conditional)
    • Example: import express from 'express'

Q4: What is module.exports and how does it work?

Answer: module.exports is the object that gets returned when you use require() to import a module. It allows you to:

  • Export functions, objects, or values
  • Make code reusable across files
  • Organize code into modules

Examples:

// Export a single function
module.exports = function greet(name) { ... }

// Export multiple items
module.exports = {
  function1,
  function2,
  variable
}

// Or using exports shortcut
exports.function1 = function() { ... }

Q5: What is the difference between synchronous and asynchronous code in Node.js?

Answer:

  • Synchronous: Code executes line by line, blocking execution until each operation completes

    • Example: fs.readFileSync() - blocks until file is read
  • Asynchronous: Code continues executing while waiting for operations to complete

    • Example: fs.readFile() - continues execution, calls callback when done
    • Uses callbacks, promises, or async/await

Why it matters: Asynchronous code allows Node.js to handle many requests concurrently without blocking.

Q6: What are Promises and async/await?

Answer:

  • Promises: Represent the eventual completion (or failure) of an asynchronous operation

    • States: pending, fulfilled, rejected
    • Methods: .then(), .catch(), .finally()
  • async/await: Syntactic sugar for Promises

    • Makes asynchronous code look synchronous
    • async functions return Promises
    • await pauses execution until Promise resolves

Example:

// Promise
fetchData()
  .then((data) => console.log(data))
  .catch((error) => console.error(error));

// async/await
async function getData() {
  try {
    const data = await fetchData();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

Express.js Concepts

Q7: What is Express.js and why do we use it?

Answer: Express.js is a minimal, flexible Node.js web application framework that provides:

  • Routing: Easy URL routing and handling
  • Middleware: Functions that execute during request/response cycle
  • HTTP Helpers: Simplified request/response handling
  • Template Engines: Support for rendering views
  • Simplified Syntax: Much easier than raw Node.js HTTP module

Why use it: It dramatically reduces boilerplate code and makes building APIs much faster and cleaner.

Q8: What is middleware in Express.js?

Answer: Middleware functions have access to:

  • Request object (req)
  • Response object (res)
  • Next function (next)

They can:

  • Execute code
  • Modify request/response objects
  • End the request-response cycle
  • Call the next middleware

Types:

  • Application-level: app.use()
  • Router-level: router.use()
  • Error-handling: 4 parameters (err, req, res, next)
  • Built-in: express.json(), express.static()
  • Third-party: cors, helmet, morgan

Q9: What is the order of middleware execution in Express?

Answer: Middleware executes in the order it's defined:

  1. Application-level middleware (in order defined)
  2. Route-specific middleware
  3. Route handler
  4. Error-handling middleware (if error occurs)

Important: Middleware order matters! For example, express.json() must come before routes that need req.body.

Q10: What is the difference between app.get() and app.use()?

Answer:

  • app.get(): Handles GET requests for a specific route

    • Example: app.get('/users', handler) - only handles GET /users
  • app.use(): Applies middleware to all HTTP methods and routes (matching or below)

    • Example: app.use('/api', middleware) - applies to all methods on /api and sub-routes

Q11: How do you handle different HTTP methods in Express?

Answer: Express provides methods for each HTTP verb:

  • app.get() - GET requests
  • app.post() - POST requests
  • app.put() - PUT requests
  • app.delete() - DELETE requests
  • app.patch() - PATCH requests
  • app.all() - All HTTP methods

Example:

app.get("/users", getUsers);
app.post("/users", createUser);
app.put("/users/:id", updateUser);
app.delete("/users/:id", deleteUser);

Q12: What is Express Router and when should you use it?

Answer: Express Router is a mini Express application that provides routing functionality. Use it to:

  • Organize routes into separate files
  • Create modular route handlers
  • Apply middleware to specific route groups
  • Improve code organization and maintainability

Example:

// routes/users.js
const router = express.Router();
router.get("/", getUsers);
router.post("/", createUser);
module.exports = router;

// app.js
app.use("/api/users", require("./routes/users"));

Backend Development Best Practices

Q13: What is separation of concerns in backend development?

Answer: Separating code into logical layers:

  • Routes: Handle HTTP requests/responses, define endpoints
  • Controllers: Business logic, process requests
  • Models/Data: Data access layer, database operations
  • Services: Reusable business logic
  • Middleware: Cross-cutting concerns (auth, validation, logging)

Benefits:

  • Easier to maintain
  • Easier to test
  • Easier to scale
  • Better code organization

Q14: How do you structure a Node.js/Express project?

Answer: Recommended structure:

project/
├── src/
│   ├── routes/       # Route definitions
│   ├── controllers/  # Business logic
│   ├── models/       # Data models
│   ├── middleware/  # Custom middleware
│   ├── services/     # Business services
│   ├── utils/        # Helper functions
│   ├── config/       # Configuration
│   └── app.js        # Express app setup
├── tests/            # Test files
├── package.json
└── .env              # Environment variables

Q15: What are environment variables and why use them?

Answer: Environment variables store configuration outside your code:

  • Security: Keep sensitive data (API keys, passwords) out of code
  • Flexibility: Different configs for dev/staging/production
  • Portability: Easy to change settings without code changes

Usage:

require("dotenv").config();
const PORT = process.env.PORT || 3000;
const DB_URL = process.env.DATABASE_URL;

Never commit .env files to git!

Q16: How do you handle errors in Express applications?

Answer: Best Practices:

  1. Try-catch blocks in async functions
  2. Error-handling middleware (4 parameters: err, req, res, next)
  3. Consistent error format across the application
  4. Appropriate HTTP status codes
  5. Error logging for debugging
  6. Don't expose internal errors to clients in production

Example:

// Error middleware (must be last)
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({
    error: err.message || "Internal Server Error",
  });
});

Q17: What is input validation and why is it important?

Answer: Input validation checks if data meets requirements before processing:

  • Security: Prevents injection attacks, XSS
  • Data Integrity: Ensures data is in correct format
  • User Experience: Provides clear error messages
  • System Stability: Prevents crashes from invalid data

Methods:

  • Manual validation (checking fields)
  • Libraries: joi, express-validator, yup
  • Validation middleware

Always validate:

  • Required fields exist
  • Data types are correct
  • Values meet constraints (e.g., positive numbers)
  • Format is valid (e.g., email format)

API Design & REST

Q18: What is REST and what are RESTful principles?

Answer: REST (Representational State Transfer) is an architectural style for APIs:

Principles:

  • Stateless: Each request contains all needed information
  • Client-Server: Clear separation of concerns
  • Uniform Interface: Consistent URL structure and HTTP methods
  • Resource-Based: URLs represent resources, not actions
  • HTTP Methods: Use GET, POST, PUT, DELETE appropriately
  • Status Codes: Use proper HTTP status codes

RESTful URLs:

  • GET /api/users - Get all users
  • GET /api/users/:id - Get user by ID
  • POST /api/users - Create user
  • PUT /api/users/:id - Update user
  • DELETE /api/users/:id - Delete user

Q19: What HTTP status codes should you use and when?

Answer: Success (2xx):

  • 200 OK - Successful GET, PUT, PATCH
  • 201 Created - Successful POST (resource created)
  • 204 No Content - Successful DELETE

Client Error (4xx):

  • 400 Bad Request - Invalid request data
  • 401 Unauthorized - Authentication required
  • 403 Forbidden - Authorized but not permitted
  • 404 Not Found - Resource doesn't exist
  • 409 Conflict - Resource conflict (e.g., duplicate)

Server Error (5xx):

  • 500 Internal Server Error - Server error

Q20: How do you design API responses consistently?

Answer: Success Response:

{
  "success": true,
  "data": { ... },
  "message": "Optional message"
}

Error Response:

{
  "success": false,
  "error": "Error message",
  "details": "Additional details"
}

Benefits:

  • Frontend can easily check success field
  • Consistent structure across all endpoints
  • Better error handling on client side

Q21: What is pagination and why is it important?

Answer: Pagination limits the number of results returned per request:

  • Performance: Prevents loading too much data
  • User Experience: Faster page loads
  • Resource Management: Reduces server load

Implementation:

// Query parameters
GET /api/products?page=1&limit=10

// Response
{
  "data": [...],
  "pagination": {
    "page": 1,
    "limit": 10,
    "total": 100,
    "totalPages": 10
  }
}

Authentication & Security

Q22: What is the difference between authentication and authorization?

Answer:

  • Authentication: Verifying WHO the user is (login)

    • "Are you really John?"
    • Usually done with username/password, tokens
  • Authorization: Verifying WHAT the user can do (permissions)

    • "Can John access this resource?"
    • Based on roles, permissions, ownership

Example:

  • User logs in (authentication) → Gets token
  • User tries to access /api/admin/users (authorization) → Check if user is admin

Q23: How does JWT (JSON Web Token) authentication work?

Answer: JWT is a token-based authentication method:

Process:

  1. User logs in with credentials
  2. Server validates credentials
  3. Server creates JWT token (contains user info)
  4. Server sends token to client
  5. Client stores token (localStorage, cookies)
  6. Client sends token in Authorization header for protected requests
  7. Server validates token and extracts user info

Token Structure:

  • Header: Algorithm and token type
  • Payload: User data (user ID, role, etc.)
  • Signature: Verifies token hasn't been tampered with

Benefits:

  • Stateless (no server-side session storage)
  • Scalable
  • Can include user info in token

Q24: Why should you never store passwords in plain text?

Answer: Security Risks:

  • If database is breached, all passwords are exposed
  • Users often reuse passwords across sites
  • Legal and compliance issues

Solution:

  • Hash passwords using libraries like bcrypt
  • Use salt (random data added before hashing)
  • Never return password hashes in API responses

Example:

const bcrypt = require("bcrypt");
const saltRounds = 10;

// Hash password
const hashedPassword = await bcrypt.hash(password, saltRounds);

// Verify password
const isValid = await bcrypt.compare(password, hashedPassword);

Q25: What is CORS and why do you need it?

Answer: CORS (Cross-Origin Resource Sharing) allows browsers to make requests to different origins (domain, protocol, or port).

Why needed:

  • Browsers block cross-origin requests by default (same-origin policy)
  • Your frontend (localhost:3000) needs to call API (localhost:5000)
  • Different domains need to communicate

Solution:

const cors = require("cors");
app.use(cors()); // Allow all origins (development)

// Production: specify allowed origins
app.use(
  cors({
    origin: "https://yourdomain.com",
  })
);

Q26: What security best practices should you follow?

Answer:

  1. HTTPS: Always use HTTPS in production
  2. Input Validation: Validate and sanitize all inputs
  3. Password Hashing: Never store plain text passwords
  4. Environment Variables: Keep secrets in .env files
  5. Rate Limiting: Prevent brute force attacks
  6. Helmet.js: Set security HTTP headers
  7. SQL Injection Prevention: Use parameterized queries
  8. XSS Prevention: Sanitize user inputs
  9. CORS Configuration: Restrict allowed origins
  10. Error Handling: Don't expose internal errors

Error Handling & Debugging

Q27: How do you debug Node.js applications?

Answer: Methods:

  1. Console.log(): Simple logging (remove in production)
  2. Debugger: Use node --inspect or VS Code debugger
  3. Logging Libraries: Winston, Pino, Morgan
  4. Error Tracking: Sentry, Rollbar
  5. Network Tools: Postman, curl, browser DevTools

Best Practices:

  • Log errors with context
  • Use log levels (info, warn, error)
  • Don't log sensitive data
  • Use structured logging

Q28: What is the difference between throw and next(error) in Express?

Answer:

  • throw error: Throws error in current execution context

    • Must be caught with try-catch
    • Works in async functions
  • next(error): Passes error to Express error-handling middleware

    • Designed for Express error handling
    • Automatically goes to error middleware

Best Practice: Use next(error) in Express route handlers to properly trigger error-handling middleware.

Q29: How do you handle async errors in Express?

Answer: Problem: Errors in async functions don't automatically go to error middleware.

Solutions:

  1. Wrap in try-catch:
app.get("/users", async (req, res, next) => {
  try {
    const users = await getUsers();
    res.json(users);
  } catch (error) {
    next(error); // Pass to error middleware
  }
});
  1. Use wrapper function:
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

app.get(
  "/users",
  asyncHandler(async (req, res) => {
    const users = await getUsers();
    res.json(users);
  })
);

Performance & Optimization

Q30: How do you improve Node.js application performance?

Answer: Strategies:

  1. Caching: Cache frequently accessed data (Redis, in-memory)
  2. Database Optimization: Use indexes, optimize queries
  3. Compression: Use compression middleware
  4. Connection Pooling: Reuse database connections
  5. Load Balancing: Distribute requests across servers
  6. CDN: Serve static files from CDN
  7. Code Optimization: Avoid blocking operations
  8. Async Operations: Use async/await properly
  9. Monitoring: Track performance metrics

Q31: What is the difference between blocking and non-blocking code?

Answer:

  • Blocking: Code stops execution until operation completes

    • Example: fs.readFileSync() - blocks until file is read
    • Blocks entire event loop
  • Non-blocking: Code continues while waiting for operation

    • Example: fs.readFile() - continues, calls callback when done
    • Doesn't block event loop

Rule: Always prefer non-blocking operations in Node.js to maintain performance.

Q32: What is middleware compression and why use it?

Answer: Compression middleware (like compression) reduces response size by compressing data:

  • Benefits: Faster response times, less bandwidth
  • Usage: app.use(compression())
  • Supports: gzip, deflate algorithms

Result: Text responses (JSON, HTML) are compressed, reducing size by 70-90%.


Database & Data Management

Q33: What is the difference between SQL and NoSQL databases?

Answer: SQL (Relational):

  • Structured data in tables
  • Relationships between tables
  • ACID compliance
  • Examples: PostgreSQL, MySQL
  • Use when: Need complex queries, relationships, transactions

NoSQL:

  • Flexible schema (document, key-value, graph)
  • Horizontal scaling
  • Examples: MongoDB, Redis
  • Use when: Need flexibility, scalability, simple structure

Q34: What is an ORM/ODM and why use it?

Answer:

  • ORM (Object-Relational Mapping): Maps database tables to JavaScript objects
  • ODM (Object-Document Mapping): Maps documents to JavaScript objects

Examples:

  • Sequelize: ORM for SQL databases
  • Mongoose: ODM for MongoDB
  • TypeORM: TypeScript ORM

Benefits:

  • Easier database queries (JavaScript instead of SQL)
  • Database abstraction
  • Migrations and schema management
  • Validation and relationships

Q35: What is connection pooling?

Answer: Connection pooling reuses database connections instead of creating new ones for each request:

  • Benefits: Better performance, efficient resource usage
  • Implementation: Most database drivers support it automatically
  • Configuration: Set pool size based on application needs

Testing & Deployment

Q36: Why is testing important for backend applications?

Answer: Benefits:

  • Catch Bugs Early: Find issues before production
  • Documentation: Tests show how code should work
  • Refactoring Safety: Ensure changes don't break functionality
  • Confidence: Deploy with assurance

Types:

  • Unit Tests: Test individual functions
  • Integration Tests: Test API endpoints
  • End-to-End Tests: Test complete flows

Q37: How do you test Express APIs?

Answer: Tools:

  • Jest: Test framework
  • Supertest: HTTP assertions for testing APIs
  • Mocha/Chai: Alternative testing framework

Example:

const request = require("supertest");
const app = require("../app");

test("GET /api/users", async () => {
  const response = await request(app).get("/api/users").expect(200);

  expect(response.body.success).toBe(true);
});

Q38: What are environment-specific configurations?

Answer: Different settings for different environments:

Development:

  • Detailed error messages
  • Debug logging
  • Local database
  • CORS allows localhost

Production:

  • Generic error messages
  • Minimal logging
  • Production database
  • CORS restricted to specific domains
  • HTTPS enabled

Implementation:

const env = process.env.NODE_ENV || "development";

if (env === "production") {
  // Production config
} else {
  // Development config
}

Common Interview Questions

Q39: Explain the Node.js event-driven architecture.

Answer: Node.js uses an event-driven, non-blocking I/O model:

  • Event Loop: Monitors call stack and callback queue
  • Single Thread: Main thread handles all operations
  • Non-blocking: I/O operations don't block execution
  • Callbacks: Functions executed when operations complete
  • Event Emitters: Objects that emit events (like HTTP requests)

Benefits:

  • Handles many concurrent connections efficiently
  • No need for thread management
  • Fast and scalable

Q40: What happens when you require a module in Node.js?

Answer:

  1. Node.js checks if module is cached
  2. If not cached, reads and executes the file
  3. Wraps code in a function with exports, require, module, __filename, __dirname
  4. Executes the wrapped code
  5. Caches the module
  6. Returns module.exports

Key Point: Modules are cached after first require, so subsequent requires return the cached version.

Q41: How do you handle file uploads in Express?

Answer: Use middleware like multer:

const multer = require("multer");
const upload = multer({ dest: "uploads/" });

app.post("/upload", upload.single("file"), (req, res) => {
  // req.file contains file information
  res.json({ file: req.file });
});

Considerations:

  • File size limits
  • File type validation
  • Storage location (local or cloud)
  • Security (scan for malware)

Q42: What is the difference between req.params, req.query, and req.body?

Answer:

  • req.params: Route parameters (from URL path)

    • Example: /users/:idreq.params.id
  • req.query: Query string parameters

    • Example: /users?page=1&limit=10req.query.page, req.query.limit
  • req.body: Request body data (POST, PUT)

    • Requires express.json() middleware
    • Contains JSON/form data

Q43: How do you implement rate limiting in Express?

Answer: Use express-rate-limit middleware:

const rateLimit = require("express-rate-limit");

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
});

app.use("/api/", limiter);

Benefits:

  • Prevents brute force attacks
  • Protects against DDoS
  • Controls API usage

Q44: What is the difference between PUT and PATCH HTTP methods?

Answer:

  • PUT: Replaces entire resource

    • Must send complete resource data
    • Idempotent (same request = same result)
  • PATCH: Partially updates resource

    • Only send fields to update
    • Not necessarily idempotent

Example:

// PUT - replace entire user
PUT /users/1
{ "name": "John", "email": "john@example.com", "age": 30 }

// PATCH - update only name
PATCH /users/1
{ "name": "Jane" }

Q45: How do you handle database transactions?

Answer: Transactions ensure multiple operations succeed or fail together:

With Sequelize (SQL):

const transaction = await sequelize.transaction();
try {
  await User.create({...}, { transaction });
  await Order.create({...}, { transaction });
  await transaction.commit();
} catch (error) {
  await transaction.rollback();
}

Benefits:

  • Data consistency
  • Atomic operations
  • Rollback on errors

Q46: What is the purpose of process.env in Node.js?

Answer: process.env is an object containing environment variables:

  • Set by operating system or .env file (with dotenv)
  • Used for configuration (port, database URLs, API keys)
  • Different values for different environments
  • Access: process.env.PORT

Best Practice: Always provide default values:

const PORT = process.env.PORT || 3000;

Q47: How do you implement pagination in an API?

Answer: Query Parameters:

// GET /api/products?page=1&limit=10
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;

const products = await Product.find().skip(skip).limit(limit);

const total = await Product.countDocuments();

res.json({
  data: products,
  pagination: {
    page,
    limit,
    total,
    totalPages: Math.ceil(total / limit),
  },
});

Q48: What is the difference between process.nextTick() and setImmediate()?

Answer:

  • process.nextTick(): Executes before any other async operation

    • Highest priority in event loop
    • Can starve event loop if used excessively
  • setImmediate(): Executes in next iteration of event loop

    • Lower priority than nextTick
    • Better for I/O operations

Rule: Use setImmediate() unless you need the highest priority (rare).

Q49: How do you implement search functionality in an API?

Answer: Basic Search:

// GET /api/products?search=laptop
const search = req.query.search || "";

const products = await Product.find({
  $or: [
    { name: { $regex: search, $options: "i" } },
    { description: { $regex: search, $options: "i" } },
  ],
});

Advanced:

  • Use full-text search indexes
  • Implement fuzzy search
  • Search across multiple fields
  • Consider search libraries (Elasticsearch)

Q50: What are the key principles of RESTful API design?

Answer:

  1. Resource-Based URLs: /users not /getUsers
  2. HTTP Methods: Use GET, POST, PUT, DELETE correctly
  3. Stateless: Each request contains all needed info
  4. Status Codes: Use appropriate HTTP status codes
  5. JSON Format: Use JSON for data exchange
  6. Versioning: /api/v1/users for API versioning
  7. Filtering/Sorting: Use query parameters
  8. Consistent Naming: Use plural nouns for resources

Study Tips

  1. Practice Regularly: Build projects, not just read
  2. Understand Concepts: Don't just memorize, understand why
  3. Read Documentation: Official docs are your best friend
  4. Code Review: Review your own and others' code
  5. Build Projects: Apply what you learn in real projects
  6. Debug Actively: Learn to debug effectively
  7. Stay Updated: Follow Node.js and Express updates

Additional Resources


Remember: Understanding these concepts deeply is more important than memorizing answers. Practice building applications and solving real problems to become a proficient backend developer! 🚀