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: docs-site/docs/general-practices/backend-endpoints.md
+13-40Lines changed: 13 additions & 40 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,11 +13,11 @@ skill_name: backend-endpoints
13
13
14
14
FinishLine's backend is an Express.js application written in TypeScript. All request handling is split into three distinct layers with clear responsibilities:
15
15
16
-
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.
17
-
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)`.
16
+
1.**Routes** define the HTTP method and path, declare validation rules using `express-validator`, and point to a controller method.
17
+
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)`.
18
18
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.
19
19
20
-
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`.
20
+
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`.
21
21
22
22
## Architecture
23
23
@@ -26,7 +26,7 @@ Two key objects are available on every request thanks to global middleware: `req
26
26
│
27
27
▼
28
28
┌──────────────┐ JWT validated, user
29
-
│ Middleware │ and organization
29
+
│ Middleware │ and organization
30
30
│ (global) │ attached to req
31
31
└──────┬───────┘
32
32
│
@@ -58,19 +58,13 @@ Two key objects are available on every request thanks to global middleware: `req
58
58
59
59
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`|
63
+
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:
72
64
73
-
For query args and transformers, see the [query-args-and-transformers](./query-args-and-transformers) skill.
|`InvalidOrganizationException(item)`| 400 | Entity not in current org |
327
-
328
-
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.
307
+
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.
-[ ] Entity name added to `ExceptionObjectNames` if needed
397
376
-[ ] All queries filter `dateDeleted: null` at every level
398
377
-[ ] Delete operations are soft deletes
399
-
-[ ] Service returns transformed shared types (see [query-args-and-transformers](./query-args-and-transformers))
378
+
-[ ] Service returns transformed shared types (see [query-args-and-transformers](../query-args-and-transformers/SKILL.md))
400
379
-[ ] Multiple writes wrapped in `prisma.$transaction()`
401
380
-[ ] All imports use `.js` extensions
402
381
-[ ] Router registered in `index.ts` (if new feature)
403
-
404
-
## Migration Notes
405
-
406
-
> New code MUST follow the patterns above. When modifying existing files, update them to match where practical.
407
-
408
-
`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.
377
+
377
378
## Step-by-Step: Creating a Full-Page Form
378
379
379
380
Full-page forms (like Work Package create/edit) skip NERFormModal and
@@ -599,23 +600,3 @@ These files demonstrate the prescribed patterns well:
599
600
-[ ] Form submit handler uses `e.stopPropagation()`
600
601
-[ ]`<form>` element has `noValidate` attribute
601
602
-[ ] Error messages display via `<FormHelperText error>`
602
-
603
-
## Migration Notes
604
-
605
-
> This section describes how this pattern differs from older code in the
606
-
> codebase. New code MUST follow the patterns above. When modifying existing
607
-
> files, update them to match these patterns where practical.
608
-
609
-
Some existing form components use `useEffect` to synchronize form state
610
-
with loaded data (e.g., `EventModal.tsx` uses `useEffect` to populate
611
-
Autocomplete selections from `initialValues` when `users` data loads).
612
-
The prescribed pattern avoids this entirely by computing all initial
613
-
values in the `defaultValues` object passed to `useForm`. When modifying
614
-
these files, replace `useEffect`-based synchronization with proper
615
-
`defaultValues` computation or direct `reset()` calls in event handlers.
616
-
617
-
Some older form modals also maintain parallel `useState` for values that
618
-
should be managed by React Hook Form (e.g., separate `useState` for
619
-
selected members alongside the form's member IDs). New forms MUST keep
620
-
all form state within React Hook Form — use `watch()` to read values
621
-
and `setValue()` to write them programmatically when needed.
0 commit comments