Project Interview Bible ⚡ Recently updatedUpdated on Jun 30, 2026 — this content may still be evolving.

Explaining API Design in Your Project

How to talk about your backend endpoints, patterns, and error handling.


API Overview Script (30 seconds)

“The backend is a REST API built with Express. It has [N] route groups: auth, users, posts, and comments. All routes are prefixed with /api/v1. Protected routes require a JWT in the Authorization header. Responses follow a consistent JSON shape with data for success and error for failures.”


Document Your Endpoints

Create a table like this for YOUR project:

MethodEndpointAuthDescription
POST/api/v1/auth/registerNoCreate account
POST/api/v1/auth/loginNoLogin, return JWT
GET/api/v1/auth/meYesCurrent user profile
GET/api/v1/postsNoList posts (paginated)
POST/api/v1/postsYesCreate post
GET/api/v1/posts/:idNoSingle post with comments
PATCH/api/v1/posts/:idYesUpdate own post
DELETE/api/v1/posts/:idYesDelete own post
POST/api/v1/posts/:id/commentsYesAdd comment
POST/api/v1/posts/:id/likeYesLike post

Interview tip: Know your endpoints without looking. Pick 3–4 to explain in detail.


Request/Response Examples

Create Post

POST /api/v1/posts
Authorization: Bearer eyJhbG...
Content-Type: application/json

{
  "title": "My First Post",
  "body": "Hello world!",
  "published": true
}
// 201 Created
{
  "data": {
    "id": "a1b2c3d4-...",
    "title": "My First Post",
    "body": "Hello world!",
    "published": true,
    "author": { "id": "...", "username": "priya" },
    "createdAt": "2025-06-30T10:00:00Z"
  }
}

Validation Error

// 400 Bad Request
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input",
    "details": [
      { "field": "title", "message": "Title is required" },
      { "field": "title", "message": "Title must be under 200 characters" }
    ]
  }
}

Pagination

GET /api/v1/posts?page=2&limit=10
{
  "data": [...],
  "meta": {
    "page": 2,
    "limit": 10,
    "total": 47,
    "totalPages": 5
  }
}

Middleware Stack (Explain This)

Request
  → cors()           // allow frontend domain
  → express.json()   // parse JSON body
  → morgan('dev')    // request logging
  → routes
      → authenticate // JWT verification (protected routes)
      → validate     // input validation (Joi/Zod)
      → controller   // business logic
  → errorHandler     // catch-all, consistent error JSON

Interview line: “Every request passes through CORS and JSON parsing globally. Protected routes add auth middleware. Validation runs before the controller. A centralized error handler catches thrown errors and returns consistent JSON — never a raw stack trace to the client.”


Validation

// Using Zod
const createPostSchema = z.object({
  title: z.string().min(1).max(200),
  body: z.string().min(1),
  published: z.boolean().optional(),
});

function validate(schema) {
  return (req, res, next) => {
    const result = schema.safeParse(req.body);
    if (!result.success) {
      return res.status(400).json({
        error: {
          code: 'VALIDATION_ERROR',
          details: result.error.issues.map(i => ({
            field: i.path.join('.'),
            message: i.message,
          })),
        },
      });
    }
    req.body = result.data;
    next();
  };
}

What to say: “I validate all inputs with Zod before they reach the controller. Invalid requests return 400 with field-level error messages — the frontend can display them next to form fields.”


Error Handling

// Centralized error handler
function errorHandler(err, req, res, next) {
  console.error(err); // log full error server-side

  if (err.name === 'ValidationError') {
    return res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: err.message } });
  }
  if (err.name === 'UnauthorizedError') {
    return res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'Invalid token' } });
  }
  if (err.code === 'P2002') { // Prisma unique constraint
    return res.status(409).json({ error: { code: 'DUPLICATE', message: 'Email already exists' } });
  }

  res.status(500).json({ error: { code: 'INTERNAL_ERROR', message: 'Something went wrong' } });
}

Authorization Patterns

RuleImplementation
Only author can edit/delete postif (post.userId !== req.user.id) return 403
Admin can delete any postif (req.user.role !== 'admin' && post.userId !== req.user.id) return 403
Users can only see own ordersWHERE user_id = req.user.id in query

Critical: Authorization happens on the server, not just hiding buttons on the frontend.


API Design Decisions to Justify

DecisionGood answer
REST vs GraphQL”REST — simple CRUD, team familiar, no complex nested queries needed”
/api/v1 prefix”Versioning — can ship v2 without breaking existing clients”
Pagination style”Offset for admin tables (< 1000 rows). Would use cursor for infinite scroll feed.”
Nested routes”/posts/:id/comments — comments belong to a post, clear hierarchy”
Status codes”201 for create, 204 for delete, 409 for duplicate email”

Common Interview Questions

Q: How does your frontend know if a request failed? A: “Axios interceptor checks response status. 401 → redirect to login. 400 → show validation errors. 500 → show generic error toast. React Query handles loading/error states per query.”

Q: How would you add rate limiting? A: “express-rate-limit middleware: 100 req/min general, 5 req/min on login. At scale, Redis-backed limiter shared across server instances.”

Q: How do you handle file uploads? A: “Client requests presigned S3 URL from POST /api/uploads/presign. Uploads directly to S3. Then POST /api/posts includes the S3 URL. API never handles file bytes — saves bandwidth.”

Q: REST vs GraphQL for your project? A: “REST fits — resource-oriented, cacheable GETs, simple client. GraphQL shines when clients need flexible nested queries. My app’s data needs are predictable.”


Checklist

  • Can list your main endpoints from memory
  • Can show one request/response example
  • Can explain middleware chain
  • Can describe validation and error handling approach
  • Can explain one authorization rule

Next: deployment.md | performance.md