Capstone topics from the end of the program: how to set up a full-stack backend project, work with npm and Git, test APIs with Postman or curl, apply layering and REST habits, and prepare for deployment and the mock interview. These patterns tie Express, SQL, and deployment lessons into day-to-day engineering practice.
Coding & architecture practices
Cross-cutting quality: separation of concerns, REST naming, consistent errors, and readable code that teammates can maintain.
Routers wire URLs, controllers handle HTTP, services own data—avoid SQL or business rules in route files.
Learn: Layers exist so each file has one reason to change. If SQL appears in a router, move it to a service. If HTTP status logic sprawls across services, return errors and map them in the controller.
Use nouns for resources, HTTP verbs for actions, and meaningful status codes (200, 201, 204, 400, 404, 500).
Learn: REST is a convention, not a framework feature. Collections use plural nouns; IDs live in the path; verbs express intent via HTTP methods—not via path names like /getUsers.
Supplementary examples
Nested resource path
GET /users/42/pastes # pastes belonging to user 42
POST /users/42/pastes # create paste for user 42
Course example
GET /users -> 200 list
POST /users -> 201 created
GET /users/:id -> 200 one | 404
PUT /users/:id -> 200 updated | 404
DELETE /users/:id -> 204 no content | 404
Use JSON error objects and appropriate status codes; centralize formatting in error middleware.
Learn: Clients and frontends depend on predictable error JSON. Pick a shape (e.g. { error: string }) and use it for validation, not found, and server errors.
// Validation
res.status(400).json({ error: "username is required" });
// Not found (in service or controller)
const err = new Error("User not found");
err.status = 404;
throw err;
// errorHandler sends: { error: err.message }
Use descriptive names, small functions, early returns, and comments only for non-obvious business rules.
Learn: Readable code reduces bugs in team settings. Prefer const, async/await over nested callbacks, and functions under ~30 lines. Rename until intent is obvious without comments.
Supplementary examples
Extract validation helper
function requireString(value, field) {
if (typeof value !== "string" || !value.trim()) {
const err = new Error(`${field} is required`);
err.status = 400;
throw err;
}
return value.trim();
}
Course example
// Prefer explicit names
async function listActiveUsers() {
return knex("users").where({ is_active: true });
}
// Prefer early return over deep nesting
if (!req.body.name) {
return res.status(400).json({ error: "name is required" });
}