Skip to content

Commit 02635be

Browse files
authored
Merge pull request #15 from flutter-news-app-full-source-code/sync-api-docs
Sync api docs
2 parents 1fb0022 + 2d1238a commit 02635be

File tree

7 files changed

+145
-67
lines changed

7 files changed

+145
-67
lines changed

astro.config.mjs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,10 @@ export default defineConfig({
6363
items: [
6464
{ label: 'Overview', link: '/api-server/architecture/' },
6565
{ label: 'Dependency Injection', link: '/api-server/architecture/dependency-injection' },
66-
{ label: 'Middleware', link: '/api-server/architecture/middleware' },
67-
{ label: 'Data Seeding & Fixtures', link: '/api-server/architecture/data-seeding-and-fixtures' },
68-
{ label: 'Generic Data Endpoint', link: '/api-server/architecture/generic-data-endpoint' },
66+
{ label: 'Middleware & Request Lifecycle', link: '/api-server/architecture/middleware' },
67+
{ label: 'Generic Data Endpoint & Registries', link: '/api-server/architecture/generic-data-endpoint' },
68+
{ label: 'Deep Dive: Data Access Flow', link: '/api-server/architecture/data-access-flow' },
69+
{ label: 'Data Seeding & System Initialization', link: '/api-server/architecture/data-seeding-and-fixtures' },
6970
{ label: 'Error Handling', link: '/api-server/architecture/error-handling' },
7071
],
7172
},
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
title: 'Architectural Deep Dive: Data Access Flow'
3+
description: A detailed walkthrough of how the API server processes a generic data request from start to finish.
4+
---
5+
6+
import { Aside, Steps } from '@astrojs/starlight/components';
7+
8+
This document provides a comprehensive, step-by-step explanation of the entire request lifecycle for the generic data endpoint (`/api/v1/data`). Understanding this flow is key to appreciating the server's robust, maintainable, and extensible design.
9+
10+
The architecture relies on a series of middleware and two central registries working in concert to process requests securely and efficiently.
11+
12+
### Core Components
13+
14+
- **`ModelRegistry`**: A map that links a model's string name (e.g., `"headline"`) to a `ModelConfig` object. This config holds metadata for authorization rules and type-specific functions (`fromJson`, `getOwnerId`). It answers the question: **"What are the rules for this model?"**
15+
16+
- **`DataOperationRegistry`**: A map that links a model's string name to the actual functions that perform CRUD operations (e.g., `create`, `read`, `update`, `delete`). It answers the question: **"How do I perform this action for this model?"**
17+
18+
- **Middleware**: A chain of functions that process the request sequentially. Each middleware has a specific responsibility, such as authentication, authorization, or data fetching.
19+
20+
---
21+
22+
### Request Lifecycle for a Collection (`GET /api/v1/data?model=...`)
23+
24+
This flow applies to requests for a collection of items.
25+
26+
<Steps>
27+
1. **Authentication (`authenticationProvider`)**
28+
The first middleware validates the `Bearer` token and injects the `User` object into the request context. If the token is invalid, the user is considered unauthenticated.
29+
30+
2. **Enforce Authentication (`requireAuthentication`)**
31+
This middleware checks if a `User` object is present in the context. Since the `/data` endpoint is protected, it will throw an `UnauthorizedException` if the user is not authenticated, aborting the request.
32+
33+
3. **Rate Limiting (`_dataRateLimiterMiddleware`)**
34+
The user's request count is checked. If the limit is exceeded, the request is aborted with a `429 Too Many Requests` error. Admins and publishers bypass this check.
35+
36+
4. **Model Validation (`_modelValidationAndProviderMiddleware`)**
37+
The `?model=` query parameter is read and validated against the `ModelRegistry`. If the model is valid, its `ModelConfig` is fetched and injected into the context for later use. If not, a `400 Bad Request` is thrown.
38+
39+
5. **Authorization (`authorizationMiddleware`)**
40+
This crucial middleware uses the `ModelConfig` to determine the required permission for a `GET` collection request. It checks if the authenticated user has this permission. If not, a `403 Forbidden` error is thrown.
41+
42+
6. **Route Handler (`/routes/api/v1/data/index.dart`)**
43+
The request finally reaches the handler. The handler uses the `DataOperationRegistry` to find the correct `readAll` function for the specified model and executes it, passing along any filter, sort, or pagination parameters.
44+
45+
7. **Response**
46+
The handler wraps the data from the repository in a standard `SuccessApiResponse` and sends it back to the client.
47+
</Steps>
48+
49+
---
50+
51+
### Request Lifecycle for a Single Item (`GET /api/v1/data/[id]?model=...`)
52+
53+
This flow is more complex, involving additional middleware to handle fetching and ownership checks.
54+
55+
<Steps>
56+
1. **Authentication, Rate Limiting, Model Validation, and Authorization**
57+
The first five steps are identical to the collection request lifecycle. The `authorizationMiddleware` checks the `getItemPermission` from the `ModelConfig`.
58+
59+
2. **Data Fetching (`dataFetchMiddleware`)**
60+
This is the first item-specific middleware. It reads the item `id` from the URL and uses the `DataOperationRegistry` to find and execute the correct `read` function for the model.
61+
- If the item is not found, it throws a `NotFoundException`.
62+
- If found, it wraps the item in a `FetchedItem` object and injects it into the context.
63+
64+
3. **Ownership Check (`ownershipCheckMiddleware`)**
65+
This middleware inspects the `ModelConfig`. If the action requires an ownership check (`requiresOwnershipCheck: true`) and the user is not an admin, it:
66+
- Reads the pre-fetched item from the context.
67+
- Uses the `getOwnerId` function from the `ModelConfig` to get the item's owner ID.
68+
- Compares the owner ID to the authenticated user's ID.
69+
- If they don't match, it throws a `403 Forbidden` error.
70+
71+
4. **Route Handler (`/routes/api/v1/data/[id]/index.dart`)**
72+
The request reaches the final handler. Because of the preceding middleware, the handler can safely assume the item exists and the user is authorized to access it. It simply reads the `FetchedItem` from the context and prepares the success response.
73+
74+
5. **Response**
75+
The handler wraps the pre-fetched item in a `SuccessApiResponse` and sends it to the client.
76+
</Steps>
77+
78+
<Aside type="note" title="PUT and DELETE Requests">
79+
The flow for `PUT` and `DELETE` requests is identical to the single item `GET` request. The only difference is that the `authorizationMiddleware` checks the `putPermission` or `deletePermission` from the `ModelConfig`, and the final route handler calls the appropriate `update` or `delete` function from the `DataOperationRegistry`.
80+
</Aside>

src/content/docs/api-server/architecture/error-handling.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ The following table lists the most common error codes and their corresponding HT
2727

2828
| HTTP Status | Error Code | Description |
2929
| :---------- | :--------------------- | :------------------------------------------------------------------------------------------------------ |
30-
| `400` | `badRequest` | The request was malformed, such as having invalid JSON or incorrect parameter formats. |
30+
| `400` | `badRequest` | The request was malformed. This can be due to invalid JSON in the body, or incorrect parameter formats (e.g., an invalid `filter` or `sort` query). |
3131
| `400` | `invalidInput` | A specific field in the request body or query parameter was invalid or missing. |
3232
| `401` | `unauthorized` | Authentication is required, but the request lacks a valid Bearer token. This can also mean the token is expired or has been invalidated (e.g., after sign-out). |
3333
| `401` | `authenticationFailed` | The provided credentials (e.g., verification code) are incorrect. |
Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,38 @@
11
---
2-
title: Generic Data Endpoint
3-
description: Understand the powerful generic data endpoint and model registry pattern used in the API server.
2+
title: 'Pattern: Generic Data Endpoint & Registries'
3+
description: Understand the powerful generic data endpoint and registry patterns used in the API server.
44
---
55

66
import { Aside } from '@astrojs/starlight/components';
77

8-
A significant architectural pattern in the API server is the use of a generic data endpoint powered by a `model_registry`. This design provides a single, consistent, and powerful interface for all standard CRUD (Create, Read, Update, Delete) operations, reducing code duplication and simplifying both the backend and client-side implementations.
8+
A significant architectural pattern in the API server is the use of a generic data endpoint powered by two central registries. This design provides a single, consistent, and powerful interface for all standard CRUD (Create, Read, Update, Delete) operations, reducing code duplication and simplifying both the backend and client-side implementations.
99

1010
### The Challenge: Avoiding Boilerplate
1111

12-
A typical REST API might have separate endpoints for each data type:
13-
- `/api/v1/headlines`
14-
- `/api/v1/topics`
15-
- `/api/v1/sources`
16-
- ...and so on.
12+
A typical REST API might have separate endpoints for each data type: `/headlines`, `/topics`, `/sources`, etc. This approach leads to a lot of boilerplate code, as the logic for handling GET, POST, PUT, and DELETE requests is often very similar for each model.
1713

18-
While clear, this approach leads to a lot of boilerplate code, as the logic for handling GET, POST, PUT, and DELETE requests is often very similar for each model.
14+
### The Solution: A Single Endpoint with Registries
1915

20-
### The Solution: A Single Endpoint with a Model Registry
21-
22-
This API server solves the problem by using a single, dynamic endpoint:
23-
24-
```
25-
/api/v1/data
26-
```
27-
28-
This endpoint handles requests for **all** standard data models. The specific model to be acted upon is determined by a `?model=` query parameter.
16+
This API server solves the problem by using a single, dynamic endpoint: `/api/v1/data`. This endpoint handles requests for all standard data models, with the specific model determined by a `?model=` query parameter.
2917

3018
- `GET /api/v1/data?model=headline` -> Returns a list of headlines.
31-
- `GET /api/v1/data/some-id?model=topic` -> Returns a single topic.
3219
- `POST /api/v1/data?model=source` -> Creates a new source.
3320

34-
### The `model_registry`
21+
This is made possible by two key components:
3522

36-
The magic behind this is the `model_registry`, a map defined in `lib/src/registry/model_registry.dart`. This registry links the string name of a model (e.g., "headline") to a `ModelConfig` object.
23+
1. **`ModelRegistry`**: This registry maps a model name (e.g., `"headline"`) to a `ModelConfig` object. The config defines the **rules** for the model, such as authorization requirements and type-specific functions (`fromJson`, `getOwnerId`).
3724

38-
Each `ModelConfig` contains:
39-
- **Type-Specific Functions:** Functions like `fromJson` to deserialize the data correctly.
40-
- **Authorization Rules:** A detailed permission configuration for each HTTP method (GET, POST, PUT, DELETE), specifying what roles or permissions are required for that action.
25+
2. **`DataOperationRegistry`**: This registry maps a model name to the actual **functions** that perform the CRUD operations (e.g., the function to fetch all headlines, the function to create a new source).
4126

42-
When a request comes in, the server's middleware looks up the `model` parameter in the registry, retrieves the correct configuration, and uses it to:
43-
1. Authorize the request based on the user's role and the action's permission requirements.
44-
2. Forward the request to the correct generic `DataRepository`.
45-
3. Deserialize and serialize data using the correct model class.
27+
When a request comes in, middleware uses the `ModelRegistry` to authorize the request and the route handlers use the `DataOperationRegistry` to execute the correct data operation.
4628

4729
<Aside type="note" title="Architectural Benefits">
48-
- **DRY (Don't Repeat Yourself):** Eliminates redundant route handlers for CRUD operations.
30+
- **DRY (Don't Repeat Yourself):** Eliminates redundant route handlers.
4931
- **Consistency:** Provides a uniform API for all data models.
50-
- **Extensibility:** Adding CRUD support for a new data model is as simple as adding a new entry to the `model_registry`.
51-
- **Centralized Authorization:** Security rules for data access are defined in one central, easy-to-audit location.
32+
- **Extensibility:** Adding CRUD support for a new model is as simple as adding entries to the two registries.
33+
- **Centralized Logic:** Authorization rules and data operations are defined in central, easy-to-audit locations.
34+
</Aside>
35+
36+
<Aside type="tip">
37+
For a complete, step-by-step walkthrough of how the endpoint, registries, and middleware work together, see the [**Architectural Deep Dive: Data Access Flow**](./data-access-flow) guide.
5238
</Aside>
Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,38 @@
11
---
2-
title: 'Advanced: Middleware'
3-
description: An explanation of the API server's middleware architecture and request lifecycle.
2+
title: 'Advanced: Middleware & Request Lifecycle'
3+
description: An explanation of the API server's middleware architecture and the detailed request lifecycle for different endpoints.
44
---
55

6-
import { Steps, Aside } from '@astrojs/starlight/components';
6+
import { Aside } from '@astrojs/starlight/components';
77

88
The API server uses a layered middleware architecture to handle requests. Middleware are functions that process a request before it reaches its final destination (the route handler). This approach allows for the separation of concerns like logging, authentication, and error handling from the core business logic.
99

10-
## Request Lifecycle
10+
An incoming request flows through a series of middleware in an "onion-skin" fashion. The outermost middleware runs first, and the innermost middleware runs last, just before the route handler.
1111

12-
An incoming request to the API server flows through a series of middleware in an "onion-skin" fashion. The outermost middleware runs first, and the innermost middleware runs last, just before the route handler.
12+
### The Middleware Chain
1313

14-
Here is the typical execution order for a request to a protected data endpoint like `/api/v1/data?model=headline`:
14+
The server defines middleware at different levels of the route tree. Here’s a breakdown of the key middleware and where they are applied:
1515

16-
<Steps>
17-
1. **Root Middleware (`/routes/_middleware.dart`)**
18-
- **Dependency Injection:** Initializes and provides all application-wide dependencies (repositories, services, etc.) into the request context. This happens once per request.
19-
- **Request ID Generation:** Assigns a unique ID to the request for logging and traceability.
20-
- **Request Logger:** Logs the incoming request details.
21-
- **Error Handler:** This is the outermost layer, wrapping the entire request to catch any exceptions thrown by inner layers and format them into a standardized JSON error response.
16+
- **Root Middleware (`/routes/_middleware.dart`)**:
17+
- `errorHandler`: The final safety net that catches all exceptions and formats them into standard JSON error responses.
18+
- `requestLogger`: Logs details of every incoming request.
19+
- `requestIdProvider`: Assigns a unique ID to each request for tracing.
20+
- `dependencyProvider`: Initializes and injects all application-wide dependencies (repositories, services, registries) into the request context.
2221

23-
2. **API v1 Middleware (`/routes/api/v1/_middleware.dart`)**
24-
- **CORS Handling:** Applies Cross-Origin Resource Sharing (CORS) headers to the response, allowing the web dashboard to communicate with the API.
25-
- **Authentication Provider:** Checks for a `Bearer` token in the `Authorization` header. If present, it validates the token and injects the corresponding `User` object (or `null`) into the request context.
22+
- **API v1 Middleware (`/routes/api/v1/_middleware.dart`)**:
23+
- `corsHeaders`: Applies Cross-Origin Resource Sharing (CORS) headers to responses.
24+
- `authenticationProvider`: Validates the `Bearer` token and injects a `User` object (or `null`) into the context.
2625

27-
3. **Data Route Middleware (`/routes/api/v1/data/_middleware.dart`)**
28-
- **Require Authentication:** Checks if a `User` object exists in the context. If not, it immediately aborts the request with a `401 Unauthorized` error.
29-
- **Rate Limiting:** If the user is not an admin or a publisher, it applies a rate limit based on the user's ID. If the limit is exceeded, it aborts with a `429 Too Many Requests` error.
30-
- **Model Validation:** Validates the `?model=` query parameter and injects the corresponding `ModelConfig` into the context.
31-
- **Authorization:** Checks if the authenticated user has the required permissions to perform the requested action on the specified model. If not, it aborts with a `403 Forbidden` error.
26+
- **Data Route Group Middleware (`/routes/api/v1/data/_middleware.dart`)**:
27+
- `requireAuthentication`: Aborts the request if no authenticated user is found.
28+
- `_dataRateLimiterMiddleware`: Applies rate limiting based on the user's ID.
29+
- `_modelValidationAndProviderMiddleware`: Validates the `?model=` query parameter and injects the `ModelConfig` and model name into the context.
30+
- `authorizationMiddleware`: Performs high-level permission checks based on the `ModelConfig`.
3231

33-
4. **Route Handler (`/routes/api/v1/data/index.dart` or `[id].dart`)**
34-
- Finally, if all middleware checks pass, the request reaches the route handler, which contains the core business logic for the endpoint (e.g., fetching data from a repository and returning it).
35-
</Steps>
32+
- **Item-Specific Middleware (`/routes/api/v1/data/[id]/_middleware.dart`)**:
33+
- `dataFetchMiddleware`: Fetches the specific item by its ID and injects it into the context.
34+
- `ownershipCheckMiddleware`: Verifies if the authenticated user is the owner of the fetched item, if required by the `ModelConfig`.
3635

37-
This layered approach ensures that by the time a request reaches its route handler, it has been logged, authenticated, authorized, and has all necessary dependencies available via the request context.
36+
<Aside type="note">
37+
For a complete, step-by-step walkthrough of how these middleware interact with the data registries, see the [**Architectural Deep Dive: Data Access Flow**](./data-access-flow) guide.
38+
</Aside>

0 commit comments

Comments
 (0)