{"post":{"slug":"how-to-build-a-rest-api-2026","title":"How to build a REST API in 2026: the complete guide","excerpt":"REST APIs are still the backbone of most web backends in 2026. This guide covers design, implementation, validation, auth, and deployment correctly.","category":"code","date":"April 14, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">R</span>EST APIs are not glamorous in 2026, but they power the majority of the web's backend communication. The fundamentals have not changed — HTTP verbs, resources, status codes — but the tooling, type safety expectations, and security requirements have. This guide builds a production-quality REST API from zero.\n\n## What changed in 2026\n\n- **TypeScript is the baseline**, not an option. Untyped Node.js API code is a code-smell in any team that has more than one developer.\n- **Zod for runtime validation** is the community default; `express-validator` and `joi` have largely been displaced.\n- **OpenAPI 3.1 spec-first development** is growing — generate the spec first, generate clients and server stubs from it.\n- **Hono is a serious alternative to Express** for edge-native APIs; the patterns below apply to both.\n- **JWT + JWKS** is the standard auth pattern for APIs consumed by frontends and third parties.\n\n## REST design fundamentals\n\n```\nGET    /api/v1/tasks          → list tasks\nPOST   /api/v1/tasks          → create a task\nGET    /api/v1/tasks/:id      → get one task\nPATCH  /api/v1/tasks/:id      → partially update a task\nDELETE /api/v1/tasks/:id      → delete a task\n```\n\nRules:\n- URLs are **nouns**, HTTP methods are **verbs**.\n- Use `PATCH` for partial updates, `PUT` for full replacement.\n- Never use verbs in URLs (`/api/createTask` is wrong).\n- Nest resources sparingly; `/tasks/:id/comments` is fine, `/users/:id/teams/:id/projects/:id/tasks` is not.\n\n## Project structure\n\n```\nsrc/\n  app.ts          # Express app (no listen call — testable)\n  server.ts       # listen here\n  routes/\n    tasks.ts\n  controllers/\n    tasks.controller.ts\n  services/\n    tasks.service.ts\n  middleware/\n    authenticate.ts\n    validate.ts\n    errorHandler.ts\n  db/\n    index.ts\n    schema.ts\n```\n\n## Setting up the stack\n\n```bash\nnpm init -y\nnpm install express zod jsonwebtoken\nnpm install -D typescript @types/express @types/node tsx vitest supertest @types/supertest\n```\n\n## A complete route with validation\n\n```ts\n// src/routes/tasks.ts\nimport { Router } from \"express\";\nimport { z } from \"zod\";\nimport { validate } from \"../middleware/validate\";\nimport * as ctrl from \"../controllers/tasks.controller\";\n\nconst router = Router();\n\nconst CreateTask = z.object({\n  body: z.object({\n    title: z.string().min(1).max(200),\n    priority: z.enum([\"low\", \"medium\", \"high\"]).default(\"medium\"),\n  }),\n});\n\nconst UpdateTask = z.object({\n  params: z.object({ id: z.coerce.number().int().positive() }),\n  body: z.object({\n    title: z.string().min(1).max(200).optional(),\n    status: z.enum([\"todo\", \"in_progress\", \"done\"]).optional(),\n  }),\n});\n\nrouter.get(\"/\", ctrl.listTasks);\nrouter.post(\"/\", validate(CreateTask), ctrl.createTask);\nrouter.patch(\"/:id\", validate(UpdateTask), ctrl.updateTask);\nrouter.delete(\"/:id\", ctrl.deleteTask);\n\nexport default router;\n```\n\n```ts\n// src/middleware/validate.ts\nimport { Request, Response, NextFunction } from \"express\";\nimport { ZodSchema, ZodError } from \"zod\";\n\nexport const validate =\n  (schema: ZodSchema) =>\n  (req: Request, res: Response, next: NextFunction) => {\n    const result = schema.safeParse({\n      body: req.body,\n      params: req.params,\n      query: req.query,\n    });\n    if (!result.success) {\n      res.status(400).json({\n        error: \"Validation failed\",\n        code: \"VALIDATION_ERROR\",\n        details: result.error.flatten(),\n      });\n      return;\n    }\n    Object.assign(req, result.data);\n    next();\n  };\n```\n\n## Error envelope convention\n\nEvery error response must have the same shape. Clients can rely on it.\n\n```ts\n// src/middleware/errorHandler.ts\nimport { Request, Response, NextFunction } from \"express\";\n\nexport function errorHandler(\n  err: Error & { statusCode?: number; code?: string },\n  _req: Request,\n  res: Response,\n  _next: NextFunction\n) {\n  const status = err.statusCode ?? 500;\n  res.status(status).json({\n    error: err.message,\n    code: err.code ?? \"INTERNAL_ERROR\",\n  });\n}\n```\n\n## Authentication pattern\n\nUse JWTs issued by a managed auth provider (Clerk, Auth0, Supabase Auth). Verify the token on every protected route:\n\n```ts\n// src/middleware/authenticate.ts\nimport { Request, Response, NextFunction } from \"express\";\nimport jwt from \"jsonwebtoken\";\n\nexport function authenticate(req: Request, res: Response, next: NextFunction) {\n  const token = req.headers.authorization?.split(\" \")[1];\n  if (!token) {\n    res.status(401).json({ error: \"Unauthorized\", code: \"NO_TOKEN\" });\n    return;\n  }\n  try {\n    const payload = jwt.verify(token, process.env.JWT_SECRET!);\n    (req as any).user = payload;\n    next();\n  } catch {\n    res.status(401).json({ error: \"Invalid token\", code: \"INVALID_TOKEN\" });\n  }\n}\n```\n\n## HTTP status code cheat sheet\n\n| Situation | Status |\n| --- | --- |\n| Created successfully | 201 |\n| No content (DELETE) | 204 |\n| Bad input / validation | 400 |\n| Not authenticated | 401 |\n| Authenticated but forbidden | 403 |\n| Resource not found | 404 |\n| Conflict (duplicate) | 409 |\n| Rate limited | 429 |\n| Server error | 500 |\n\n## How to pick between REST and alternatives\n\n| API style | Best for | Avoid for |\n| --- | --- | --- |\n| REST | Standard CRUD, wide clients | Real-time, graph-shaped data |\n| GraphQL | Graph-heavy data, flexible queries | Simple CRUD |\n| tRPC | Full-stack TypeScript monorepos | Public third-party APIs |\n| gRPC | Service-to-service, performance | Browser clients |\n\nREST is the right default when building a public API or serving clients you do not control.\n\n## Common mistakes\n\n**Returning 200 for errors.** Clients rely on HTTP status codes to route error handling. A 200 with `{ success: false }` breaks every HTTP client library's default behavior.\n\n**No pagination on list endpoints.** Return `{ data: [], meta: { total, page, limit } }` from day one. Adding pagination to an unpaginated API is a breaking change.\n\n**Skipping rate limiting.** Add `express-rate-limit` or an upstream API gateway rate limit before any route is public. A single user can knock over an unprotected API.\n\n**Exposing internal IDs everywhere.** Sequential integer IDs leak record counts and enable enumeration attacks. Use UUIDs or ULIDs for resources exposed in URLs.\n\n## What to skip\n\n- **Custom session management** — JWT with a managed provider is more secure and less code.\n- **SOAP or XML responses** — no new API should be built on SOAP in 2026.\n- **Over-nesting routes** — more than two levels of resource nesting (`/a/:id/b/:id/c`) is a design smell; flatten or use query params.\n\n## FAQ\n\n**Should I use REST or GraphQL in 2026?**\nREST for public APIs and simple CRUD. GraphQL when your data is genuinely graph-shaped or your clients need flexible query shapes without over-fetching.\n\n**How do I document my REST API?**\nGenerate an OpenAPI 3.1 spec. Use Scalar or Swagger UI to render it. Libraries like `zod-openapi` can generate the spec from your Zod schemas automatically.\n\n**How do I handle file uploads in a REST API?**\nAccept `multipart/form-data` with `multer`. Stream directly to S3-compatible storage; never buffer large files in memory on the server.\n\n**What is the right way to handle pagination?**\nCursor-based pagination (return a `nextCursor`) scales better than offset-based for large or frequently updated datasets. Offset is fine for small, stable datasets.\n\n## Where to go next\n\n- [How to set up a database in 2026](/blog/how-to-set-up-a-database-2026)\n- [How to handle API errors in 2026](/blog/how-to-handle-api-errors-2026)\n- [How to learn Express in 2026](/blog/how-to-learn-express-2026)","hue":322,"metaTitle":"How to Build a REST API in 2026: Node.js and TypeScript","metaDescription":"Complete 2026 guide to building a REST API with Node.js, Express, TypeScript, and Zod. Covers design, auth, validation, error handling, and deployment.","keywords":["how to build a rest api 2026","rest api tutorial node js 2026","express rest api typescript","rest api design best practices 2026","rest api authentication 2026","rest api validation zod","rest api error handling","build backend api 2026","node js api tutorial 2026","rest vs graphql 2026"],"tldr":["**Design your URL structure first** — resources are nouns, HTTP verbs are actions; get this right before writing a single line of code.","**Validate every input with Zod** (or equivalent) before it touches your database or business logic.","**Return consistent error shapes** — a `{ error: string, code: string }` envelope on every error response makes client integration predictable.","**Version from day one** — `/api/v1/` in the path; you will need it.","**Skip:** custom authentication from scratch — use a managed auth provider (Clerk, Auth0) or a battle-tested JWT library; rolling your own is a security liability."],"readTime":"5 min read","wordCount":1055},"related":[{"slug":"how-to-build-a-rest-api-in-node-2026","title":"How to build a REST API in Node in 2026: the complete guide","excerpt":"Building a production-ready REST API in Node.js in 2026 means choosing the right framework, structuring routes cleanly, and shipping validation and auth from day one.","category":"code","date":"March 11, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":318},{"slug":"how-to-learn-express-2026","title":"How to learn Express in 2026: the no-nonsense guide","excerpt":"Express remains the backbone of countless Node.js APIs in 2026, even as Hono and Fastify gain ground. Here is how to learn it efficiently and correctly.","category":"code","date":"March 13, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":316},{"slug":"best-api-design-practices-2026","title":"Best API design practices in 2026: build APIs people enjoy using","excerpt":"A practical guide to designing APIs in 2026 - REST versus alternatives, versioning, pagination, errors, and the conventions that keep clients happy.","category":"code","date":"June 3, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":205}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"how-to-build-a-pc-2026","title":"How to build a PC in 2026: parts, budget, and step-by-step assembly","excerpt":"Building a PC is still the best way to get exactly the machine you want for your budget. Here is the 2026 parts guide and assembly walkthrough.","category":"guides","date":"April 14, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":95},"older":{"slug":"how-to-build-a-rss-feed-2026","title":"How to build a RSS feed in 2026","excerpt":"RSS feeds are having a quiet resurgence in 2026 — newsletters, podcasts, and AI aggregators all consume them. This guide covers building a valid RSS 2.0 and Atom feed for any web framework.","category":"code","date":"April 14, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":322}}