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.

4 topic(s) on this page.

How to separate concerns across layers — Intermediate

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.

Supplementary examples

Controller maps service error to 404

try {
  const row = await service.read(id);
  res.json({ data: row });
} catch (err) {
  next(err); // errorHandler reads err.status
}

Course example

// Good: router only connects URL to controller
router.route("/").get(controller.list).post(controller.create);

// Avoid: database logic inline in router
// router.get("/", async (req, res) => {
//   const rows = await knex("users").select();  // move to service
// });

Additional references & examples

How to follow REST conventions — Basic

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

Additional references & examples

How to return consistent API errors — Intermediate

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.

Supplementary examples

errorHandler shape

function errorHandler(err, req, res, next) {
  const status = err.status || 500;
  res.status(status).json({
    error: err.message,
    ...(process.env.NODE_ENV === "development" && { stack: err.stack }),
  });
}

Course example

// 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 }

Additional references & examples

How to write readable backend code — Basic

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" });
}

Additional references & examples