You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .claude/skills/general-practices/backend-endpoints/SKILL.md
+12-39Lines changed: 12 additions & 39 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -11,11 +11,11 @@ description: Guide for creating backend API endpoints in FinishLine following th
11
11
12
12
FinishLine's backend is an Express.js application written in TypeScript. All request handling is split into three distinct layers with clear responsibilities:
13
13
14
-
1.**Routes** define the HTTP method and path, declare validation rules using `express-validator`, and point to a controller method. They contain zero business logic.
15
-
2.**Controllers** are thin glue — they extract data from `req.params`, `req.body`, and `req.query`, call the appropriate service method, and return the result as JSON. They MUST delegate all errors to Express via `next(error)`.
14
+
1.**Routes** define the HTTP method and path, declare validation rules using `express-validator`, and point to a controller method.
15
+
2.**Controllers** are lightweight wrappers around the service functions. They extract data from `req.params`, `req.body`, and `req.query`, call the appropriate service method, and return the result as JSON. They also catch service errors and delegate to Express via `next(error)`.
16
16
3.**Services** contain all business logic: permission checks, database queries via Prisma, data transformation, and side effects (Slack notifications, Google integrations, etc.). Services throw custom exceptions when something goes wrong.
17
17
18
-
Two key objects are available on every request thanks to global middleware: `req.currentUser` (the authenticated `User`) and `req.organization` (the Prisma `Organization` record). These are set by the `getUserAndOrganization` middleware in `src/backend/index.ts` and typed via `src/backend/custom.d.ts`.
18
+
Two key objects are available on every request from middleware: `req.currentUser` (the authenticated `User`) and `req.organization` (the Prisma `Organization` record). These are set by the `getUserAndOrganization` middleware in `src/backend/index.ts` and typed via `src/backend/custom.d.ts`.
19
19
20
20
## Architecture
21
21
@@ -24,7 +24,7 @@ Two key objects are available on every request thanks to global middleware: `req
24
24
│
25
25
▼
26
26
┌──────────────┐ JWT validated, user
27
-
│ Middleware │ and organization
27
+
│ Middleware │ and organization
28
28
│ (global) │ attached to req
29
29
└──────┬───────┘
30
30
│
@@ -56,19 +56,13 @@ Two key objects are available on every request thanks to global middleware: `req
56
56
57
57
If a service throws an exception, it bubbles up through the controller's `next(error)` call and is caught by the global `errorHandler` middleware registered at the bottom of `src/backend/index.ts`.
| Express types |`src/backend/custom.d.ts`|`currentUser` and `organization` on `Request`|
61
+
Finishline uses a layer-based architecture, where 3 folders (routes, controllers, services) contain all the files related to that layer for every domain. The naming convention looks like:
70
62
71
-
For query args and transformers, see the [query-args-and-transformers](../query-args-and-transformers/SKILL.md) skill.
|`InvalidOrganizationException(item)`| 400 | Entity not in current org |
325
-
326
-
The `name` parameter for `NotFoundException` and `DeletedException` MUST be one of the values in the `ExceptionObjectNames` type union in `errors.utils.ts`. Add your entity to that type if it's not listed.
305
+
Services throw exceptions from `src/backend/src/utils/errors.utils.ts`. The global `errorHandler` middleware catches them. The `name` parameter for `NotFoundException` and `DeletedException` MUST be one of the values in the `ExceptionObjectNames` type union in `errors.utils.ts`. Add your entity to that type if it's not listed.
-[ ] Multiple writes wrapped in `prisma.$transaction()`
399
378
-[ ] All imports use `.js` extensions
400
379
-[ ] Router registered in `index.ts` (if new feature)
401
-
402
-
## Migration Notes
403
-
404
-
> New code MUST follow the patterns above. When modifying existing files, update them to match where practical.
405
-
406
-
`work-packages.routes.ts` contains one endpoint using the `DELETE` HTTP method (`workPackagesRouter.delete('/:wbsNum/delete', ...)`). This is legacy. The prescribed pattern is `POST` for all mutations including deletions. When touching this endpoint, migrate it to `POST`.
NOTE: while `CreateXModal.tsx` and `EditXModal.tsx` will typically be dealing with the same payload structure to pass to their mutation hooks, this is not always the case. To allow for differences between create and edit functionality, you can create a type with optional fields that contain all the data needed for both create and update mutations, and use that as the input parameter type for your onSubmit. In the form, you can determine if you are are editting or creating based on the detault values. For an example of these concepts look at the calendar event form.
375
+
375
376
## Step-by-Step: Creating a Full-Page Form
376
377
377
378
Full-page forms (like Work Package create/edit) skip NERFormModal and
@@ -597,23 +598,3 @@ These files demonstrate the prescribed patterns well:
597
598
-[ ] Form submit handler uses `e.stopPropagation()`
598
599
-[ ]`<form>` element has `noValidate` attribute
599
600
-[ ] Error messages display via `<FormHelperText error>`
600
-
601
-
## Migration Notes
602
-
603
-
> This section describes how this pattern differs from older code in the
604
-
> codebase. New code MUST follow the patterns above. When modifying existing
605
-
> files, update them to match these patterns where practical.
606
-
607
-
Some existing form components use `useEffect` to synchronize form state
608
-
with loaded data (e.g., `EventModal.tsx` uses `useEffect` to populate
609
-
Autocomplete selections from `initialValues` when `users` data loads).
610
-
The prescribed pattern avoids this entirely by computing all initial
611
-
values in the `defaultValues` object passed to `useForm`. When modifying
612
-
these files, replace `useEffect`-based synchronization with proper
613
-
`defaultValues` computation or direct `reset()` calls in event handlers.
614
-
615
-
Some older form modals also maintain parallel `useState` for values that
616
-
should be managed by React Hook Form (e.g., separate `useState` for
617
-
selected members alongside the form's member IDs). New forms MUST keep
618
-
all form state within React Hook Form — use `watch()` to read values
619
-
and `setValue()` to write them programmatically when needed.
0 commit comments