Traversy Media Node/Express REST API Builder
Build a fully structured RESTful CRUD API with Node.js, Express, and PostgreSQL using clean separation of routes and controllers, testable via Postman.
// TL;DR
The Traversy Media Node/Express REST API Builder is a step-by-step skill for scaffolding a fully structured CRUD API using Node.js, Express, and PostgreSQL. It enforces clean separation between routes and controllers, uses UUIDs for unique record IDs, and relies on Postman for testing all HTTP verbs. Use it whenever you need to create a new REST API from scratch, add CRUD endpoints to an existing Express project, or refactor a messy single-file server into a maintainable routes/controllers architecture.
// When should you use the Node/Express REST API Builder?
Use this skill whenever you need to scaffold a new REST API from scratch, add CRUD endpoints to an existing Node/Express project, or structure backend route logic cleanly using the routes/controllers pattern.
// What inputs do you need before building a Node/Express REST API?
- Project namerequired
Name of the API project / folder - Resource namerequired
The primary data entity the API will manage (e.g. users, products, posts) - Resource schemarequired
The fields that define one record of the resource (e.g. firstName, lastName, age) - Port number
Local port the server should listen on (default recommendation: 5000 for backend) - Database type
Real database (e.g. PostgreSQL, MongoDB) or mock in-memory array for prototyping
// What core principles guide clean REST API architecture in Express?
CRUD as the spine
Every API resource needs exactly five operations: get all, get one by ID, create, update (patch), and delete. Design routes around these before writing any logic.
Routes vs Controllers separation
Routes belong in a /routes folder and should contain nothing but the HTTP verb, path, and a reference to a handler function. All logic lives in /controllers. Mixing them causes files you can get lost in.
Partial modification with PATCH, full overwrite with PUT
Use PATCH when the client may send only the fields they want to change. Use PUT only when the entire resource is being replaced. PATCH is the correct default for profile-update style endpoints.
nodemon for developer experience
Install nodemon as a dev dependency and wire it to an npm start script so the server auto-restarts on every file save. Never manually restart the server during development.
Postman for non-GET testing
Browsers can only make GET requests. Use Postman to test POST, PATCH, and DELETE routes. Set the body to raw JSON and select the JSON content-type header.
UUID for guaranteed unique IDs
Never rely on sequential integers for IDs in a Node app. Import uuid v4 and call it at record-creation time. The resulting string is cryptographically unique and will never collide.
body-parser middleware for request bodies
Initialise body-parser with app.use(bodyParser.json()) before any routes. Without it, req.body is undefined on POST and PATCH requests.
// How do you build a Node/Express REST API step by step?
- 1
Initialise the project
Run `npm init -y` in an empty folder. Add `"type": "module"` to package.json to enable ES module import/export syntax. Install express and body-parser: `npm install express body-parser`. Install nodemon as a dev dependency: `npm install --save-dev nodemon`. Add a start script: `"start": "nodemon index.js"`.
- 2
Create and configure the Express server in index.js
Import express and body-parser. Initialise the app with `const app = express()`. Set a PORT constant (recommend 5000 to avoid clashing with frontend on 3000). Call `app.use(bodyParser.json())`. Add `app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`))`. Add a sanity-check GET route at `/` returning a simple string. Run `npm start` and confirm the console message appears.
- 3
Define the resource schema
Write out the fields for one record of your resource (e.g. firstName, lastName, age for a user). This is the shape every object in the database will have. Keep a reference file (e.g. user.json) using double-quoted keys and values as a reminder — JSON requires double quotes, no trailing commas.
- 4
Create the routes file
Create a /routes folder. Inside, create {resource}.js. Import express and initialise `const router = express.Router()` (capital R). Stub out all five routes using router.get, router.post, router.get('/:id'), router.delete('/:id'), router.patch('/:id'). Each stub should res.send a short label string so you can verify routing works before adding logic. Export default router.
- 5
Mount the router in index.js
Import the routes file: `import usersRoutes from './routes/users.js'` (include the .js extension). Mount with `app.use('/users', usersRoutes)`. IMPORTANT: because you mount at '/users', every path inside the routes file must start with just '/' — not '/users'. '/users' + '/' = '/users'. '/users' + '/users' = '/users/users' which is wrong.
- 6
Create the controllers file
Create a /controllers folder. Inside, create {resource}.js. This file holds the in-memory array (or later, DB connection calls) and all handler functions. Each handler is an exported const arrow function accepting (req, res). Move all logic out of routes into correspondingly named functions: getUsers, getUser, createUser, deleteUser, updateUser.
- 7
Implement getUsers (GET all)
In the controller, export a getUsers function that calls res.send(users) where users is the in-memory array. In the route, import getUsers and pass it as the callback: `router.get('/', getUsers)`.
- 8
Implement createUser (POST)
Install uuid: `npm install uuid`. Import `{ v4 as uuidv4 }` in the controller. In createUser, read `const user = req.body`. Construct a new object: `{ ...user, id: uuidv4() }`. Push it to the users array. Respond with a template-string confirmation: `res.send(`User with the name ${user.firstName} added to the database`)`. Test in Postman: POST to /users, body → raw → JSON, include all schema fields.
- 9
Implement getUser (GET one by ID)
In getUser, extract `const { id } = req.params`. Use `const foundUser = users.find(u => u.id === id)`. Return `res.send(foundUser)`. Test by copying a real ID from a GET-all response and appending it to the URL in Postman or the browser.
- 10
Implement deleteUser (DELETE by ID)
Declare users with `let` (not `const`) at the top of the controller because the array reference will be reassigned. In deleteUser, extract id from req.params. Reassign: `users = users.filter(u => u.id !== id)`. Respond: `res.send(`User with the id ${id} deleted from the database`)`. Test with a DELETE request in Postman — browsers cannot send DELETE requests.
- 11
Implement updateUser (PATCH by ID)
In updateUser, extract id from req.params. Find the target: `const user = users.find(u => u.id === id)`. Destructure the incoming fields from req.body (e.g. firstName, lastName, age). For each field, write a conditional: `if (firstName) user.firstName = firstName`. Repeat for every mutable field. Never allow the client to overwrite the id field. Respond with a confirmation template string. Test in Postman with a PATCH request, sending only the fields you want to change — unchanged fields must remain intact.
- 12
Verify the full route/controller structure
The routes file should contain only imports, router declarations, and five one-liner route registrations. All logic, the users array, and uuid imports live exclusively in the controllers file. Run through all five operations end-to-end in Postman before considering the API complete.
// What are real-world examples of this REST API pattern in action?
A developer needs a CRUD API for a products resource with fields: name, price, category.
Follow steps 1-2 to bootstrap the server. Define the schema as { name, price, category }. Create /routes/products.js with five stubbed router methods. Mount at app.use('/products', productsRoutes). In /controllers/products.js, declare let products = []. Implement getProducts (res.send array), createProduct (spread req.body + uuidv4 id, push, confirm string), getProduct (find by req.params.id), deleteProduct (filter out by id, reassign let), updateProduct (find, conditionally overwrite name/price/category). Test each verb in Postman with the body set to raw JSON.
A developer has a working but messy single-file Express app where routes and logic are all in index.js.
Extract all route callback functions into a /controllers/{resource}.js file as named exported arrow functions. Create a /routes/{resource}.js file using express.Router(), import the controller functions, and register them against the correct HTTP verbs and paths. In index.js, replace inline callbacks with app.use('/{resource}', routerImport). The index.js file should now only contain server setup: imports, middleware, route mounts, and app.listen.
// What common mistakes should you avoid when building an Express REST API?
- Mounting the router at '/users' in index.js AND writing '/users' as the path inside the routes file — this creates '/users/users'. Paths inside the router must start with just '/'.
- Declaring the users array with const when deleteUser needs to reassign it via filter — always use let for any array that will be reassigned.
- Forgetting to include the .js file extension on local imports when using ES modules (`"type": "module"` in package.json) — Node will throw a module-not-found error.
- Forgetting `app.use(bodyParser.json())` before routes — req.body will be undefined on all POST and PATCH requests.
- Testing POST, PATCH, or DELETE routes from a browser URL bar — browsers only make GET requests. Always use Postman for non-GET verbs.
- In Postman, forgetting to set the body to 'raw' and the format to 'JSON' — the server receives no body data.
- Allowing the client to overwrite the id field in the PATCH handler — only conditionally update mutable fields; never touch id.
- Not restarting the server after adding nodemon — run npm start once and let nodemon handle all subsequent restarts on save.
// What key terms should you know for Node/Express REST API development?
- REST API
- Representational State Transfer API — a set of rules developers follow when creating APIs so that programs can talk to each other in a standardised way.
- CRUD
- Create, Read, Update, Delete — the four fundamental operations every data-driven API must support.
- Express
- A fast, unopinionated, minimalist web framework for Node.js used to create routes and handle HTTP requests.
- Router
- An Express object (express.Router()) that lets you group related routes in a separate file and mount them under a common path prefix in index.js.
- Controllers
- A /controllers folder containing files that hold all handler logic, separated from the routing layer. Routes reference controller functions by name.
- Routes
- A /routes folder containing files that define only the HTTP verb, path, and which controller function to call — no logic lives here.
- req.body
- The parsed JSON payload sent by the client on POST and PATCH requests. Requires body-parser middleware to be populated.
- req.params
- An object containing named URL segments prefixed with a colon in the route path (e.g. '/:id'). Used to extract dynamic values like a record's ID.
- nodemon
- A dev-only tool that watches the project for file changes and automatically restarts the Node server, eliminating manual restarts during development.
- uuid (v4)
- A package that generates a cryptographically unique string ID each time it is called, ensuring no two records ever share the same ID.
- PATCH
- An HTTP method used for partial modification of a resource — only the fields sent in the request body are updated; all others remain unchanged.
- PUT
- An HTTP method used to completely overwrite a resource. Distinct from PATCH, which is for partial updates.
- Postman
- A desktop tool for making HTTP requests of any verb (GET, POST, PATCH, DELETE, etc.) to test API endpoints, since browsers are limited to GET requests.
- body-parser
- An Express middleware module that parses incoming request bodies as JSON and exposes the result on req.body.
- Mock database
- An in-memory JavaScript array used during development to simulate a real database. Data resets every time the server restarts.
// FREQUENTLY ASKED QUESTIONS
What is the Traversy Media Node/Express REST API Builder?
It is a structured skill for building a complete CRUD REST API using Node.js, Express, and PostgreSQL. It walks you through project initialization, route and controller separation, schema definition, and endpoint implementation for get-all, get-one, create, update (PATCH), and delete operations, all testable via Postman.
What is the routes and controllers pattern in Express?
The routes/controllers pattern separates your Express app into two layers. Route files live in a /routes folder and contain only the HTTP verb, path, and a reference to a handler function. Controller files live in a /controllers folder and hold all the business logic, database queries, and response formatting. This separation keeps files small and maintainable.
How do I build a REST API with Node.js and Express from scratch?
Initialize a project with npm init, install Express and body-parser, set up an entry file (index.js) with middleware and a listener, define your resource schema, create route stubs in /routes, implement handler logic in /controllers, mount the router in index.js, and test every endpoint in Postman. Use nodemon for auto-restart during development and uuid for unique IDs.
How do I separate routes and controllers in an Express app?
Create a /routes folder with a file per resource that uses express.Router() to define HTTP verbs and paths. Create a /controllers folder with matching files that export named handler functions. In each route, import the corresponding controller function and pass it as the callback. Mount the router in index.js with app.use('/resource', router).
How does this Node/Express REST API approach compare to using a framework like NestJS?
This approach is lightweight and unopinionated — you manually set up routes, controllers, and middleware with plain Express. NestJS provides built-in dependency injection, decorators, modules, and TypeScript-first architecture out of the box. Choose this Express-based approach for smaller projects or when you want full control; choose NestJS for large, enterprise-grade applications that benefit from enforced structure.
When should I use this skill to build an API instead of a different approach?
Use it whenever you need to scaffold a new REST API from scratch, add CRUD endpoints to an existing Node/Express project, refactor a messy single-file server into clean route/controller separation, or prototype a backend with an in-memory mock database before connecting a real database like PostgreSQL. It's ideal for MVPs, learning projects, and small-to-medium production APIs.
What results can I expect after following this REST API workflow?
You will have a fully functional CRUD API with five endpoints (get all, get one, create, update, delete) for any resource. The codebase will be cleanly separated into routes and controllers, every record will have a UUID, and all endpoints will be verified via Postman. The architecture is ready to swap the in-memory array for a real database.
Why does req.body show undefined in my Express POST request?
req.body is undefined because body-parser middleware is not registered before your routes. Add app.use(bodyParser.json()) in your index.js file before any app.use() calls that mount your route files. Without this middleware, Express does not parse incoming JSON request bodies, so req.body remains undefined on all POST and PATCH requests.
Should I use PATCH or PUT for updating data in a REST API?
Use PATCH when the client sends only the fields it wants to change — unchanged fields remain intact. Use PUT when the client sends the entire resource to completely replace it. For most use cases like profile updates or editing a product, PATCH is the correct default because it avoids accidentally nullifying fields the client did not include in the request.
Why use UUID instead of auto-incrementing IDs in a Node.js API?
Auto-incrementing integer IDs are managed by databases, not by Node.js in-memory logic. In a Node app — especially one using a mock array — sequential IDs are unreliable after deletions. UUID v4 generates a cryptographically unique string each time, ensuring no collisions regardless of whether you're using an in-memory array or a real database.