-
Notifications
You must be signed in to change notification settings - Fork 30
/
0020-api-route-signature.md
126 lines (91 loc) · 4.91 KB
/
0020-api-route-signature.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
- Start Date: 2022-04-25
- Reference Issues:
- Implementation PR: <!-- leave empty -->
# Summary
Change the signature for API routes to accept a single argument containing the file-based routing params, the [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request), and any other objects we might need to add in the future.
# Example
With the new API, instead of params being the first argument and request the second, they are combined into a single context object that contains both.
```js
export async function get({ params, request }) {
if(!request.headers.has('cookie')) {
return new Response(null, {
status: 301,
headers: {
Location: '/'
}
});
}
// ...
}
```
# Motivation
API Routes were originally created for the use-case of generating non-HTML files during a SSG (static-site generation) build, before Astro had support for SSR (server-side rendering). This allowed you to create Atom files, JSON files, and such.
The original API provided [params](https://docs.astro.build/en/reference/api-reference/#params) as the only argument to an API route. When SSR was implemented there was a need to pass in the [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object in order to read headers, such as the Cookie header to do authentication.
Often times the Request object is needed but the params are not, due to the route not being a dynamic route, which leads to writing code like the following, to ignore the first argument.
```js
export async function get(_, request) {
if(!request.headers.has('cookie')) {
return new Response(null, {
status: 301,
headers: {
Location: '/'
}
});
}
}
```
As we look to add more parameters in the future you can see this situation getting worse; for example if we add some facility to make cookie reading/writing easier that could lead to a 3rd argument where the first two are ignored.
# Detailed design
The new API route signature should contain 1 argument, a `APIContext` which looks like this:
```ts
interface APIContext {
request: Request;
params: Params;
}
```
This type can be imported from the `astro` package and used like so:
```ts
import type { APIContext } from 'astro';
export function get({ request }: APIContext) {
if(!request.headers.has('cookie')) {
return new Response(null, {
status: 301,
headers: {
Location: '/'
}
});
}
}
```
In the future you could see APIContext being expanded to add additional properties, such as a read/write cookie interface, without needing to adjust user code.
## Types
There are 2 types provided in the proposal:
- `APIContext` is the context object. You can use it like so:
```ts
import type { APIContext } from 'astro';
export function post(ctx: APIContext) {
// ...
}
```
- `APIRoute` is the signature of each route, and can be used like shown below. Notice that using this method we do not need to set the type of the APIContext argument; by setting the type of the function the arguments and return value are known by TypeScript.
```ts
import type { APIRoute } from 'astro';
export const get: APIRoute = async({ request }) => {
return new Response(null, { status: 404 });
}
```
# Drawbacks
- This would be a breaking change at some point before 1.0. Since we are in the beta period this is not ideal. See the __Adoption Strategy__ section for a plan to mitigate this drawback.
# Alternatives
On Discord some alternatives were proposed:
- Swapping the first and second arguments (`params` and `request`) since `request` is needed more. However it's only needed more in SSR, in SSG `params` is more likely to be needed, so it's the same issue just in reverse.
- Making `request` be the first argument a context object (containing params) be the second. This has the same drawbacks as the first alternative but is also still a breaking change. Having the first and only argument be a context object seems like the most future-proof option.
## Prior art
Other frameworks have adopted the single-argument form.
- __[Remix](https://remix.run/docs/en/v1/guides/api-routes#call-loaders-outside-of-navigation)__ takes a single argument that is an object containing the `request` in its `loader` API.
- __[SvelteKit](https://kit.svelte.dev/docs/types#additional-types-requestevent)__ endpoints take a single argument that is an object containing the `request`, `params` (which mirrors our params object), and other contextual information.
# Adoption strategy
This is a breaking change to take place during the beta period, however we can still provide a good backwards-compatible experience before making the final breaking change.
1. Continue to support the 2-argument form while providing a warning for those using this signature.
1. Use a `Proxy` to detect users of the single-argument `get(params)` form and provide a good warning for them.
1. After some period, before 1.0 is finalized, drop the previous signature completely.