Skip to content

Commit 1aac773

Browse files
committed
bring updates from documentation onto this branch
1 parent d5b19b0 commit 1aac773

3 files changed

Lines changed: 19 additions & 65 deletions

File tree

docs-site/docs/general-practices/backend-endpoints.md

Lines changed: 13 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ skill_name: backend-endpoints
1313

1414
FinishLine's backend is an Express.js application written in TypeScript. All request handling is split into three distinct layers with clear responsibilities:
1515

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)`.
1818
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.
1919

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`.
2121

2222
## Architecture
2323

@@ -26,7 +26,7 @@ Two key objects are available on every request thanks to global middleware: `req
2626
2727
2828
┌──────────────┐ JWT validated, user
29-
│ Middleware │ and organization
29+
│ Middleware │ and organization
3030
│ (global) │ attached to req
3131
└──────┬───────┘
3232
@@ -58,19 +58,13 @@ Two key objects are available on every request thanks to global middleware: `req
5858

5959
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`.
6060

61-
## File Locations
61+
## File Structure
6262

63-
| Layer | Path | Naming |
64-
| ------------- | ------------------------------------------------------ | --------------------------------------------- |
65-
| Entry point | `src/backend/index.ts` ||
66-
| Routes | `src/backend/src/routes/{feature}.routes.ts` | `{feature}Router` |
67-
| Controllers | `src/backend/src/controllers/{feature}.controllers.ts` | `{Feature}Controller` class |
68-
| Services | `src/backend/src/services/{feature}.services.ts` | `{Feature}Service` class |
69-
| Validation | `src/backend/src/utils/validation.utils.ts` | Shared validators |
70-
| Errors | `src/backend/src/utils/errors.utils.ts` | `HttpException` subclasses |
71-
| 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:
7264

73-
For query args and transformers, see the [query-args-and-transformers](./query-args-and-transformers) skill.
65+
- `src/backend/src/routes/{feature}.routes.ts`
66+
- `src/backend/src/controllers/{feature}.controllers.ts`
67+
- `src/backend/src/services/{feature}.services.ts`
7468

7569
## How Endpoint URLs Work
7670

@@ -181,12 +175,10 @@ export default class CalendarController {
181175

182176
- Every method MUST be `static async` with signature `(req: Request, res: Response, next: NextFunction)`.
183177
- Every method body MUST be wrapped in `try { ... } catch (error: unknown) { next(error); }`.
184-
- NEVER handle errors directly in the controller. Always call `next(error)`.
185178
- Extract URL params with: `const { id } = req.params as Record<string, string>;`
186179
- **Parse date strings to `Date` objects in the controller** before passing to the service: `new Date(startTime)`.
187-
- Always pass `req.currentUser` and `req.organization` to service methods that need them.
180+
- Pass `req.currentUser` and `req.organization` to service methods that need them.
188181
- Return `res.status(200).json(result)` for all successful responses.
189-
- Controllers contain ZERO business logic — no permission checks, no database queries.
190182

191183
### Step 4: Write the Service Method
192184

@@ -274,7 +266,7 @@ export default class CalendarService {
274266
- ALWAYS filter `dateDeleted: null` on queries at both the top level and within nested includes/selects.
275267
- Deleting an entity MUST be a soft delete (`dateDeleted: new Date()`), never `prisma.*.delete()`.
276268

277-
For query args and transformer patterns, see the [query-args-and-transformers](./query-args-and-transformers) skill.
269+
For query args and transformer patterns, see the [query-args-and-transformers](../query-args-and-transformers/SKILL.md) document.
278270

279271
### Step 5: Deciding the Access Level
280272

@@ -312,20 +304,7 @@ Match the exception class to the level: `AccessDeniedGuestException` for `notGue
312304

313305
## Error Handling
314306

315-
Services throw exceptions from `src/backend/src/utils/errors.utils.ts`. The global `errorHandler` middleware catches them.
316-
317-
| Exception | Status | When to Use |
318-
| ---------------------------------------- | ------ | ----------------------------------------- |
319-
| `HttpException(status, msg)` | any | General-purpose with custom status |
320-
| `NotFoundException(name, id)` | 404 | Entity not found |
321-
| `DeletedException(name, id)` | 404 | Entity is soft-deleted |
322-
| `AccessDeniedException(msg?)` | 403 | Generic permission failure |
323-
| `AccessDeniedAdminOnlyException(action)` | 403 | Non-admin attempting admin action |
324-
| `AccessDeniedMemberException(action)` | 403 | Guest/member attempting restricted action |
325-
| `AccessDeniedGuestException(action)` | 403 | Guest attempting non-guest action |
326-
| `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.
329308

330309
## Validation Helpers
331310

@@ -396,13 +375,7 @@ For complex reusable validators, spread them: `...descriptionBulletsValidators`.
396375
- [ ] Entity name added to `ExceptionObjectNames` if needed
397376
- [ ] All queries filter `dateDeleted: null` at every level
398377
- [ ] 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))
400379
- [ ] Multiple writes wrapped in `prisma.$transaction()`
401380
- [ ] All imports use `.js` extensions
402381
- [ ] 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`.

docs-site/docs/general-practices/frontend-forms.md

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,6 @@ data changes.
7171
- **NERDraggableFormModal:** `src/frontend/src/components/NERDraggableFormModal.tsx`
7272
- **ReactHookTextField:** `src/frontend/src/components/ReactHookTextField.tsx`
7373
- **ReactHookEditableList:** `src/frontend/src/components/ReactHookEditableList.tsx`
74-
- **Modal forms:** `src/frontend/src/pages/{Feature}/{Components}/XFormModal.tsx`
75-
- **Full-page forms:** `src/frontend/src/pages/{Feature}Form/XFormView.tsx`
7674

7775
## Core Concepts
7876

@@ -91,8 +89,7 @@ management. It accepts these key props:
9189
NERFormModal internally:
9290

9391
1. Wraps `onFormSubmit` to call `reset()` after the submit callback
94-
2. Calls `reset()` when the modal is closed via `onHide`
95-
3. Renders a `<form>` element with `noValidate` and `e.stopPropagation()`
92+
2. Renders a `<form>` element with `noValidate` and `e.stopPropagation()`
9693

9794
### useForm Setup
9895

@@ -210,6 +207,8 @@ For `Autocomplete` (multi-select):
210207
/>
211208
```
212209

210+
NOTE: For taking in a user as an input to a form, always prefer autocomplete over select, and in general only show members as options.
211+
213212
## Step-by-Step: Creating a Modal Form
214213

215214
### Step 1: Define the Form Values Interface and Schema
@@ -374,6 +373,8 @@ const EditThingModal = ({ showModal, handleClose, thing }: EditThingModalProps)
374373
};
375374
```
376375

376+
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+
377378
## Step-by-Step: Creating a Full-Page Form
378379

379380
Full-page forms (like Work Package create/edit) skip NERFormModal and
@@ -599,23 +600,3 @@ These files demonstrate the prescribed patterns well:
599600
- [ ] Form submit handler uses `e.stopPropagation()`
600601
- [ ] `<form>` element has `noValidate` attribute
601602
- [ ] 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.

docs-site/docs/general-practices/frontend-hooks-and-apis.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ export const getAllEvents = () => {
192192
};
193193
```
194194

195-
See the [query-args-and-transformers](./query-args-and-transformers) for detailed transformer patterns.
195+
See the [query-args-and-transformers skill](../../general-practices/query-args-and-transformers/SKILL.md) for detailed transformer patterns.
196196

197197
### Step 4: Write the Query Hook
198198

0 commit comments

Comments
 (0)