Skip to content

Commit 2e77043

Browse files
committed
docs: add API wrapper design spec
1 parent c186d01 commit 2e77043

1 file changed

Lines changed: 91 additions & 0 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Front-end API Wrapper Design
2+
3+
**Date:** 2026-06-03
4+
**Issue:** Define a Common Interface for Front-end API Requests
5+
6+
## Problem
7+
8+
Axios is imported and called directly in at least 8 places across the front-end (`store/index.js`, `CampaignCards.vue`, `CauseCarousel.vue`, `DonateMoney.vue`, `LetterLoad.vue`, `SearchReps.vue`, `SignName.vue`, `vue_logger.js`). There is no consistent error type, no shared base-URL logic, and no seam to mock in tests.
9+
10+
## Solution
11+
12+
Create `src/api/index.js` — a class-based wrapper following the existing `PaymentPresenter` / `PaymentPresenterError` pattern in `shared/presenters/payment-presenter.js`.
13+
14+
## API Module (`src/api/index.js`)
15+
16+
### `APIError`
17+
18+
Extends `Error`. Sets `this.name = 'APIError'` and optionally carries `this.status` (HTTP status code) for callers that need to branch on it.
19+
20+
```js
21+
class APIError extends Error {
22+
constructor(message, status) {
23+
super(message)
24+
this.name = 'APIError'
25+
this.status = status
26+
}
27+
}
28+
```
29+
30+
### `API` class
31+
32+
Constructor accepts:
33+
- `path` (string, required) — resource path, e.g. `'/campaigns'`
34+
- `version` (string, optional) — API version segment, e.g. `'v1'`
35+
36+
Builds `baseUrl`: `/api/v1/campaigns` (with version) or `/api/campaigns` (without).
37+
38+
Exposes four methods mirroring HTTP verbs:
39+
- `get(endpoint = '', params = {})` — appends endpoint to baseUrl, passes params as query string
40+
- `post(endpoint = '', data = {})` — POST to baseUrl + endpoint
41+
- `put(endpoint = '', data = {})` — PUT to baseUrl + endpoint
42+
- `delete(endpoint = '')` — DELETE to baseUrl + endpoint
43+
44+
All methods:
45+
1. `await` the axios call
46+
2. Return `response.data` on success
47+
3. Catch errors and rethrow as `APIError(error.message, error.response?.status)`
48+
49+
### Usage examples
50+
51+
```js
52+
// Versioned resource
53+
const letterTemplates = new API('/letter_templates', 'v1')
54+
const rendered = await letterTemplates.post('/render', { mergeVariables, templateId })
55+
56+
// Unversioned resource
57+
const representatives = new API('/representatives')
58+
const reps = await representatives.get(`/${postalCode}`)
59+
```
60+
61+
## Tests (`src/api/__tests__/api.test.js`)
62+
63+
Using Jest with `jest.mock('axios')`.
64+
65+
| Test | Assertion |
66+
|------|-----------|
67+
| URL construction with version | `baseUrl` equals `/api/v1/path` |
68+
| URL construction without version | `baseUrl` equals `/api/path` |
69+
| Successful GET returns `response.data` | resolved value equals mocked data |
70+
| Successful POST returns `response.data` | resolved value equals mocked data |
71+
| Axios error is rethrown as `APIError` | thrown instance is `APIError` |
72+
| `APIError` carries HTTP status | `error.status` matches mocked response status |
73+
74+
## Refactoring Scope
75+
76+
Migrate as examples of the new pattern — not a wholesale migration:
77+
78+
1. **`src/store/index.js` `loadLetterTemplate` action** — uses `/api/v1/letter_templates/:id` (v1 route)
79+
2. **`src/components/LetterLoad.vue` `renderLetter` method** — uses `/api/v1/letter_templates/render` (v1 route)
80+
81+
All other existing axios calls (SearchReps, DonateMoney, SignName, CampaignCards, CauseCarousel, vue_logger) are left for a follow-up migration issue.
82+
83+
## File Structure
84+
85+
```
86+
src/
87+
api/
88+
index.js ← new
89+
__tests__/
90+
api.test.js ← new
91+
```

0 commit comments

Comments
 (0)