Module 43: Backend Architecture

Lesson 2 of 5 · 15–25 minutes

Controllers and services

🏠The everyday picture

A restaurant's waiter (controller) takes the order and brings the food, but doesn't cook. The chef (service) cooks by the recipe, without ever talking to diners.

📘What it really is

// src/routes/enquiries.ts
enquiriesRouter.patch("/:id/close", async (req, res) => {
  const input = closeSchema.parse(req.body);           // Lesson 4
  const enquiry = await enquiryService.close(req.params.id, input.note, req.user);
  res.json(enquiry);
});
// src/services/enquiryService.ts
export async function close(id: string, note: string, user: User): Promise<Enquiry> {
  const enquiry = await enquiryRepo.findById(id);
  if (!enquiry) throw new NotFoundError("Enquiry not found");
  if (enquiry.status === "closed") throw new ConflictError("Enquiry is already closed");
  const updated = { ...enquiry, status: "closed" as const, closedBy: user.id, closedNote: note };
  return enquiryRepo.save(updated);
}

🖱️Try it

Create services/enquiryService.ts with functions for list, get, create, update status and delete. Move all rules into it. Make each route handler short: read input, call one service function, respond.

✅Quick check

Should a service function receive req and res?

Show the answer

No. Services know nothing about HTTP; controllers pass them plain values.

Why are services easier to test than route handlers?

Show the answer

They're plain functions with no HTTP, so tests call them directly with values.

← PreviousWhy structure matters
ProLearners Assist