@fastly/expressly 1.0.0--canary.4.2464710183.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
  2. package/.github/workflows/release.yml +85 -0
  3. package/.husky/pre-commit +4 -0
  4. package/.nvmrc +1 -0
  5. package/LICENSE +21 -0
  6. package/README.md +56 -0
  7. package/SECURITY.MD +9 -0
  8. package/dist/index.d.ts +4 -0
  9. package/dist/index.js +5 -0
  10. package/dist/lib/routing/common.d.ts +11 -0
  11. package/dist/lib/routing/common.js +36 -0
  12. package/dist/lib/routing/error-middleware.d.ts +10 -0
  13. package/dist/lib/routing/error-middleware.js +12 -0
  14. package/dist/lib/routing/errors.d.ts +9 -0
  15. package/dist/lib/routing/errors.js +16 -0
  16. package/dist/lib/routing/request/cookie-map.d.ts +9 -0
  17. package/dist/lib/routing/request/cookie-map.js +37 -0
  18. package/dist/lib/routing/request/index.d.ts +27 -0
  19. package/dist/lib/routing/request/index.js +47 -0
  20. package/dist/lib/routing/request-handler.d.ts +10 -0
  21. package/dist/lib/routing/request-handler.js +12 -0
  22. package/dist/lib/routing/response/index.d.ts +24 -0
  23. package/dist/lib/routing/response/index.js +101 -0
  24. package/dist/lib/routing/response/status-codes.d.ts +56 -0
  25. package/dist/lib/routing/response/status-codes.js +57 -0
  26. package/dist/lib/routing/response/surrogate-keys.d.ts +9 -0
  27. package/dist/lib/routing/response/surrogate-keys.js +29 -0
  28. package/dist/lib/routing/router.d.ts +32 -0
  29. package/dist/lib/routing/router.js +165 -0
  30. package/docs/.nvmrc +1 -0
  31. package/docs/README.md +27 -0
  32. package/docs/babel.config.js +3 -0
  33. package/docs/docs/config.md +52 -0
  34. package/docs/docs/handling-data/_category_.json +4 -0
  35. package/docs/docs/handling-data/cookies.md +103 -0
  36. package/docs/docs/handling-data/headers.md +110 -0
  37. package/docs/docs/handling-data/request.md +82 -0
  38. package/docs/docs/handling-data/response.md +162 -0
  39. package/docs/docs/handling-data/search-params.md +45 -0
  40. package/docs/docs/intro.md +61 -0
  41. package/docs/docs/middleware/_category_.json +5 -0
  42. package/docs/docs/middleware/controlling-flow.md +41 -0
  43. package/docs/docs/middleware/error-middleware.md +41 -0
  44. package/docs/docs/middleware/what-is-middleware.md +31 -0
  45. package/docs/docs/origin-data/_category_.json +4 -0
  46. package/docs/docs/origin-data/working-with-origins.md +49 -0
  47. package/docs/docs/routing.md +163 -0
  48. package/docs/docusaurus.config.js +119 -0
  49. package/docs/package.json +48 -0
  50. package/docs/sidebars.js +31 -0
  51. package/docs/src/components/HomepageFeatures.module.css +11 -0
  52. package/docs/src/components/HomepageFeatures.tsx +62 -0
  53. package/docs/src/css/custom.css +45 -0
  54. package/docs/src/pages/index.module.css +35 -0
  55. package/docs/src/pages/index.tsx +109 -0
  56. package/docs/static/.nojekyll +0 -0
  57. package/docs/static/img/favicon.ico +0 -0
  58. package/docs/static/img/logo-black.png +0 -0
  59. package/docs/static/img/logo-transparent.png +0 -0
  60. package/docs/static/img/logo.png +0 -0
  61. package/docs/static-host/Cargo.lock +435 -0
  62. package/docs/static-host/Cargo.toml +16 -0
  63. package/docs/static-host/fastly.toml +9 -0
  64. package/docs/static-host/rust-toolchain +3 -0
  65. package/docs/static-host/src/main.rs +115 -0
  66. package/docs/tsconfig.json +7 -0
  67. package/docs/yarn.lock +8416 -0
  68. package/package.json +56 -0
  69. package/src/index.ts +11 -0
  70. package/src/lib/routing/common.ts +34 -0
  71. package/src/lib/routing/error-middleware.ts +23 -0
  72. package/src/lib/routing/errors.ts +18 -0
  73. package/src/lib/routing/index.d.ts +18 -0
  74. package/src/lib/routing/request/cookie-map.ts +42 -0
  75. package/src/lib/routing/request/index.ts +64 -0
  76. package/src/lib/routing/request-handler.ts +22 -0
  77. package/src/lib/routing/response/index.ts +113 -0
  78. package/src/lib/routing/response/status-codes.ts +57 -0
  79. package/src/lib/routing/response/surrogate-keys.ts +32 -0
  80. package/src/lib/routing/router.ts +191 -0
  81. package/tsconfig.json +20 -0
@@ -0,0 +1,57 @@
1
+ // See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
2
+ export const statusText = {
3
+ 100: "Continue",
4
+ 101: "Switching Protocols",
5
+ 103: "Early Hints",
6
+ 200: "OK",
7
+ 201: "Created",
8
+ 202: "Accepted",
9
+ 203: "Non-Authoritative Information",
10
+ 204: "No Content",
11
+ 205: "Reset Content",
12
+ 206: "Partial Content",
13
+ 300: "Multiple Choices",
14
+ 301: "Moved Permanently",
15
+ 302: "Found",
16
+ 303: "See Other",
17
+ 304: "Not Modified",
18
+ 307: "Temporary Redirect",
19
+ 308: "Permanent Redirect",
20
+ 400: "Bad Request",
21
+ 401: "Unauthorized",
22
+ 402: "Payment Required",
23
+ 403: "Forbidden",
24
+ 404: "Not Found",
25
+ 405: "Method Not Allowed",
26
+ 406: "Not Acceptable",
27
+ 407: "Proxy Authentication Required",
28
+ 408: "Request Timeout",
29
+ 409: "Conflict",
30
+ 410: "Gone",
31
+ 411: "Length Required",
32
+ 412: "Precondition Failed",
33
+ 413: "Payload Too Large",
34
+ 414: "URI Too Long",
35
+ 415: "Unsupported Media Type",
36
+ 416: "Range Not Satisfiable",
37
+ 417: "Expectation Failed",
38
+ 418: "I'm a teapot",
39
+ 422: "Unprocessable Entity",
40
+ 425: "Too Early",
41
+ 426: "Upgrade Required",
42
+ 428: "Precondition Required",
43
+ 429: "Too Many Requests",
44
+ 431: "Request Header Fields Too Large",
45
+ 451: "Unavailable For Legal Reasons",
46
+ 500: "Internal Server Error",
47
+ 501: "Not Implemented",
48
+ 502: "Bad Gateway",
49
+ 503: "Service Unavailable",
50
+ 504: "Gateway Timeout",
51
+ 505: "HTTP Version Not Supported",
52
+ 506: "Variant Also Negotiates",
53
+ 507: "Insufficient Storage",
54
+ 508: "Loop Detected",
55
+ 510: "Not Extended",
56
+ 511: "Network Authentication Required"
57
+ };
@@ -0,0 +1,9 @@
1
+ /// <reference types="@fastly/js-compute" />
2
+ export declare class SurrogateKeys extends Set {
3
+ private headers;
4
+ constructor(headers: Headers);
5
+ clear(): void;
6
+ add(key: string): this;
7
+ delete(key: string): boolean;
8
+ private serialize;
9
+ }
@@ -0,0 +1,29 @@
1
+ export class SurrogateKeys extends Set {
2
+ constructor(headers) {
3
+ super();
4
+ this.headers = headers;
5
+ }
6
+ clear() {
7
+ this.headers.delete("Surrogate-Key");
8
+ super.clear();
9
+ }
10
+ add(key) {
11
+ super.add(key);
12
+ this.serialize();
13
+ return this;
14
+ }
15
+ delete(key) {
16
+ const deleteResult = super.delete(key);
17
+ this.serialize();
18
+ return deleteResult;
19
+ }
20
+ serialize() {
21
+ if (this.size) {
22
+ const keys = [];
23
+ for (const [key] of this.entries()) {
24
+ keys.push(key);
25
+ }
26
+ this.headers.set("Surrogate-Key", keys.join(" "));
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,32 @@
1
+ import { EConfig } from ".";
2
+ import { RequestHandler, RequestHandlerCallback } from "./request-handler";
3
+ import { ErrorMiddleware, ErrorMiddlewareCallback } from "./error-middleware";
4
+ export declare class Router {
5
+ requestHandlers: Array<RequestHandler>;
6
+ errorHandlers: Array<ErrorMiddleware>;
7
+ config: EConfig;
8
+ constructor(config?: EConfig);
9
+ listen(): void;
10
+ private handler;
11
+ use(path: string | RequestHandlerCallback | ErrorMiddlewareCallback, callback?: RequestHandlerCallback | ErrorMiddlewareCallback): void;
12
+ route(methods: string[], pattern: string, callback: RequestHandlerCallback): void;
13
+ all(pattern: string, callback: RequestHandlerCallback): void;
14
+ get(pattern: string, callback: RequestHandlerCallback): void;
15
+ post(pattern: string, callback: RequestHandlerCallback): void;
16
+ put(pattern: string, callback: RequestHandlerCallback): void;
17
+ delete(pattern: string, callback: RequestHandlerCallback): void;
18
+ head(pattern: string, callback: RequestHandlerCallback): void;
19
+ options(pattern: string, callback: RequestHandlerCallback): void;
20
+ patch(pattern: string, callback: RequestHandlerCallback): void;
21
+ private runRequestHandlers;
22
+ private runErrorHandlers;
23
+ /**
24
+ * Creates a function used to check if the request method and path match a router configuration.
25
+ * @param methods An array of HTTP method(s) or "*" to match all methods.
26
+ * @param pattern A path string compatible with path-to-regexp (see: https://www.npmjs.com/package/path-to-regexp)
27
+ * @param extractRequestParameters Whether to extract parameters from a request
28
+ * @returns 405 if the method is not allowed, 404 if the path doesn't match, 0 otherwise.
29
+ */
30
+ private routeMatcher;
31
+ private static serializeResponse;
32
+ }
@@ -0,0 +1,165 @@
1
+ import { match } from "path-to-regexp";
2
+ import { RequestHandler } from "./request-handler";
3
+ import { ErrorMiddleware } from "./error-middleware";
4
+ import { ErrorNotFound, ErrorMethodNotAllowed } from "./errors";
5
+ import { ERequest } from "./request";
6
+ import { EResponse } from "./response";
7
+ const pathMatcherCache = new Map();
8
+ const defaultErrorHandler = (auto405) => async (err, req, res) => {
9
+ if (err instanceof ErrorNotFound || (err instanceof ErrorMethodNotAllowed && !auto405)) {
10
+ return res.sendStatus(404);
11
+ }
12
+ else if (err instanceof ErrorMethodNotAllowed) {
13
+ res.headers.set("Allow", err.allow);
14
+ return res.sendStatus(405);
15
+ }
16
+ res.withStatus(500).json({ error: err });
17
+ };
18
+ export class Router {
19
+ constructor(config) {
20
+ this.requestHandlers = [];
21
+ this.errorHandlers = [];
22
+ this.config = {
23
+ parseCookie: true,
24
+ auto405: true,
25
+ extractRequestParameters: true,
26
+ autoContentType: false
27
+ };
28
+ this.config = {
29
+ ...this.config,
30
+ ...config
31
+ };
32
+ }
33
+ listen() {
34
+ addEventListener("fetch", (event) => event.respondWith(this.handler(event)));
35
+ }
36
+ async handler(event) {
37
+ const req = new ERequest(this.config, event);
38
+ const res = new EResponse(this.config);
39
+ try {
40
+ // Run middleware and request handler stack.
41
+ await this.runRequestHandlers(req, res);
42
+ }
43
+ catch (err) {
44
+ // Add default error handler.
45
+ this.use(defaultErrorHandler(this.config.auto405));
46
+ // Run error handler stack.
47
+ await this.runErrorHandlers(err, req, res);
48
+ }
49
+ return Router.serializeResponse(res);
50
+ }
51
+ // Middleware attach point.
52
+ use(path, callback) {
53
+ const cb = path instanceof Function ? path : callback;
54
+ const matcher = path instanceof Function ? () => 0 : this.routeMatcher(["*"], path);
55
+ if (cb.length === 3) {
56
+ this.errorHandlers.push(new ErrorMiddleware(matcher, cb));
57
+ }
58
+ else {
59
+ this.requestHandlers.push(new RequestHandler(matcher, cb));
60
+ }
61
+ }
62
+ // Router API.
63
+ route(methods, pattern, callback) {
64
+ this.requestHandlers.push(new RequestHandler(this.routeMatcher(methods.map(m => m.toUpperCase()), pattern), callback));
65
+ }
66
+ all(pattern, callback) {
67
+ this.route(["*"], pattern, callback);
68
+ }
69
+ get(pattern, callback) {
70
+ this.route(["GET"], pattern, callback);
71
+ }
72
+ post(pattern, callback) {
73
+ this.route(["POST"], pattern, callback);
74
+ }
75
+ put(pattern, callback) {
76
+ this.route(["PUT"], pattern, callback);
77
+ }
78
+ delete(pattern, callback) {
79
+ this.route(["DELETE"], pattern, callback);
80
+ }
81
+ head(pattern, callback) {
82
+ this.route(["HEAD"], pattern, callback);
83
+ }
84
+ options(pattern, callback) {
85
+ this.route(["OPTIONS"], pattern, callback);
86
+ }
87
+ patch(pattern, callback) {
88
+ this.route(["PATCH"], pattern, callback);
89
+ }
90
+ // Request handler runner.
91
+ async runRequestHandlers(req, res) {
92
+ let checkResult;
93
+ const allowedMethods = [];
94
+ for (let a of this.requestHandlers) {
95
+ if (res.hasEnded) {
96
+ break;
97
+ }
98
+ checkResult = a.check(req);
99
+ if (checkResult === 0) {
100
+ await a.run(req, res);
101
+ }
102
+ else if (checkResult.length) {
103
+ allowedMethods.push(checkResult);
104
+ }
105
+ }
106
+ if (allowedMethods.length) {
107
+ throw new ErrorMethodNotAllowed(allowedMethods);
108
+ }
109
+ else if (checkResult === 404) {
110
+ throw new ErrorNotFound();
111
+ }
112
+ }
113
+ // Error handler runner.
114
+ async runErrorHandlers(err, req, res) {
115
+ for (let eH of this.errorHandlers) {
116
+ if (res.hasEnded) {
117
+ break;
118
+ }
119
+ if (eH.check(req) === 0) {
120
+ await eH.run(err, req, res);
121
+ }
122
+ }
123
+ }
124
+ /**
125
+ * Creates a function used to check if the request method and path match a router configuration.
126
+ * @param methods An array of HTTP method(s) or "*" to match all methods.
127
+ * @param pattern A path string compatible with path-to-regexp (see: https://www.npmjs.com/package/path-to-regexp)
128
+ * @param extractRequestParameters Whether to extract parameters from a request
129
+ * @returns 405 if the method is not allowed, 404 if the path doesn't match, 0 otherwise.
130
+ */
131
+ routeMatcher(methods, pattern) {
132
+ return (req) => {
133
+ const methodAllowed = methods.some(m => m === "*" || m === req.method);
134
+ if (pattern === "*" || pattern === "(.*)") {
135
+ return methodAllowed ? 0 : methods;
136
+ }
137
+ else {
138
+ // Cache pattern matcher.
139
+ if (!pathMatcherCache.has(pattern)) {
140
+ pathMatcherCache.set(pattern, match(pattern, { decode: decodeURIComponent }));
141
+ }
142
+ // Match on pathname.
143
+ let { path, params } = pathMatcherCache.get(pattern)(req.url.pathname) || {};
144
+ if (path) {
145
+ if (this.config.extractRequestParameters) {
146
+ req.params = params;
147
+ }
148
+ return methodAllowed ? 0 : methods;
149
+ }
150
+ }
151
+ // Route not found.
152
+ return 404;
153
+ };
154
+ }
155
+ static serializeResponse(res) {
156
+ // Default to 200 / 204 if no status was set by middleware / route handler.
157
+ if (res.status === 0) {
158
+ res.status = Boolean(res.body) ? 200 : 204;
159
+ }
160
+ return new Response(res.body, {
161
+ headers: res.headers,
162
+ status: res.status,
163
+ });
164
+ }
165
+ }
package/docs/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 16
package/docs/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # expressly documentation
2
+
3
+ The [**expressly** documentation website](https://expressly.edgecompute.app) is built using [Docusaurus](https://docusaurus.io/).
4
+
5
+ ### Installation
6
+
7
+ ```
8
+ $ yarn install
9
+ ```
10
+
11
+ ### Local development
12
+
13
+ ```
14
+ $ yarn start
15
+ ```
16
+
17
+ This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
18
+
19
+ To make changes, edit the Markdown files in [`docs/`](./docs).
20
+
21
+ ### Build
22
+
23
+ ```
24
+ $ yarn build
25
+ ```
26
+
27
+ This command generates static content into the `build` directory.
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
3
+ };
@@ -0,0 +1,52 @@
1
+ ---
2
+ sidebar_position: 7
3
+ ---
4
+
5
+ # Configuration
6
+
7
+ **expressly**'s router can be initialized with an optional configuration object:
8
+
9
+ ```javascript
10
+ const router = new Router({
11
+ parseCookie: true,
12
+ auto405: true,
13
+ extractRequestParameters: true,
14
+ autoContentType: true,
15
+ });
16
+ ```
17
+
18
+ ## Options
19
+
20
+ ### parseCookie
21
+
22
+ > Default 🟢 `true`
23
+
24
+ When set to `true`, enables parsing of the `Cookie` request header and exposes [`req.cookies`](handling-data/cookies.md#request-cookies).
25
+
26
+ ### auto405
27
+
28
+ > Default 🟢 `true`
29
+
30
+ When set to `true`, **expressly** will respond with HTTP `405 Method Not Allowed` and automatically set the [`Allow` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Allow) if it cannot match the client request method on a matched path.
31
+
32
+ In the example below, a `POST` request to the `/users` path will result in a HTTP 405 response.
33
+
34
+ ```javascript
35
+ const router = new Router({ auto405: true });
36
+ router.get("/users", (req, res) => { ... });
37
+ router.listen();
38
+ ```
39
+
40
+ Setting `auto405: false` above will cause **expressly** to respond with HTTP 404.
41
+
42
+ ### extractRequestParameters
43
+
44
+ > Default 🟢 `true`
45
+
46
+ When set to `true`, exposes `req.params`, an object containing properties mapped to any [named route "parameters"](./routing#path-parameters).
47
+
48
+ ### autoContentType
49
+
50
+ > Default 🔴 `false` 🔥 experimental
51
+
52
+ When set to `true`, [`res.send`](handling-data/response.md#ressend) will try to [infer the `Content-Type` header](https://expressjs.com/en/4x/api.html#res.send) from the data it is passed, if the header is not set.
@@ -0,0 +1,4 @@
1
+ {
2
+ "label": "Handling data",
3
+ "position": 4
4
+ }
@@ -0,0 +1,103 @@
1
+ ---
2
+ sidebar_position: 4
3
+ ---
4
+
5
+ # Cookies
6
+
7
+ ## Request cookies
8
+
9
+ You can retrieve the cookies sent by the client by accessing the `req.cookies` object, which implements the [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) interface.
10
+
11
+ ```javascript
12
+ router.get("/", (req, res) => {
13
+ res.json({
14
+ session: req.cookies.get("session")
15
+ })
16
+ })
17
+ ```
18
+
19
+ The `req.cookies.entries()` method returns an iterator for all cookie key/value pairs.
20
+
21
+ ```javascript
22
+ // List all cookie names and values.
23
+ for (const [name, value] of req.cookies.entries()) {
24
+ console.log(`${name}=${value}`);
25
+ }
26
+ ```
27
+
28
+ If you need only the keys or values, use `req.cookies.keys()` or `req.cookies.values()`, respectively.
29
+
30
+ Use `req.cookies.delete(name)` to delete a cookie:
31
+
32
+ ```javascript
33
+ router.get("/", (req, res) => {
34
+ req.cookies.delete("auth");
35
+ const beresp = await fetch(req, { backend: "my-origin" });
36
+ // ...
37
+ })
38
+ ```
39
+
40
+ ## Response cookies
41
+
42
+ Setting response cookies is done by calling `res.cookie(key, value[, options])`.
43
+
44
+ ```javascript
45
+ router.get("/", (req, res) => {
46
+ res.cookie("user", "me");
47
+ res.cookie("auth", "AUTH_TOKEN_HERE", { maxAge: 3600, path: "/" })
48
+ res.send("Cookie set!")
49
+ })
50
+ ```
51
+
52
+ Calling `res.clearCookie(key[, options])` will expire a response cookie:
53
+
54
+ ```javascript
55
+ router.get("/", (req, res) => {
56
+ res.clearCookie("auth", { path: "/" })
57
+ res.send("Cookie expired!")
58
+ })
59
+ ```
60
+
61
+ ### Options
62
+
63
+ `res.cookie()` accepts the following properties in the `options` object:
64
+
65
+ #### [domain](https://tools.ietf.org/html/rfc6265#section-5.2.3)
66
+
67
+ Specifies the `string` value for the `Domain` attribute. By default, no domain is set, and most clients will consider the cookie to apply to only the current domain.
68
+
69
+ #### [expires](https://tools.ietf.org/html/rfc6265#section-5.2.1)
70
+
71
+ A `Date` object or `string` that determines the value for the `Expires` attribute. By default, this is not set, and most clients will consider this a "non-persistent cookie".
72
+
73
+ > **Note:** The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and
74
+ `maxAge` are set, then `maxAge` takes precedence.
75
+
76
+ #### [maxAge](https://tools.ietf.org/html/rfc6265#section-5.2.2)
77
+
78
+ A `number` (in seconds) that determines the value for the `Max-Age` attribute. By default, no maximum cookie age is set.
79
+
80
+ #### [httpOnly](https://tools.ietf.org/html/rfc6265#section-5.2.6)
81
+
82
+ A `boolean` value. When truthy, the `HttpOnly` attribute is set, otherwise it is not. By default, the `HttpOnly` attribute is not set.
83
+
84
+ #### [path](https://tools.ietf.org/html/rfc6265#section-5.2.4)
85
+
86
+ Specifies the `string` value for the `Path` attribute.
87
+
88
+ #### [sameSite](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7)
89
+
90
+ Specifies the `boolean` or `string` value for the `SameSite` attribute:
91
+
92
+ - `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
93
+ - `false` will not set the `SameSite` attribute.
94
+ - `'lax'` will set the `SameSite` attribute to `Lax` for lax same site enforcement.
95
+ - `'none'` will set the `SameSite` attribute to `None` for an explicit cross-site cookie.
96
+ - `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
97
+
98
+ > **Note:** This is an attribute that has not yet been fully standardized, and may change in the future.
99
+ This also means many clients may ignore this attribute until they understand it.
100
+
101
+ #### [secure](https://tools.ietf.org/html/rfc6265#section-5.2.5)
102
+
103
+ Specifies the `boolean` value for the `Secure` attribute. When truthy, the `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set.
@@ -0,0 +1,110 @@
1
+ ---
2
+ sidebar_position: 3
3
+ ---
4
+
5
+ # Headers
6
+
7
+ **expressly** simplifies header manipulation on `Request` and `Response` objects.
8
+
9
+ > 🚨 **Unlike Express**, both `req.headers` and `res.headers` implement the standard [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) interface of the Fetch API.
10
+
11
+ ## Get header values
12
+
13
+ Use `*.headers.get(name)` to retrieve the values of a HTTP header.
14
+
15
+ > 💡 **Header names are case-insensitive**!
16
+
17
+ ```javascript
18
+ router.get("/", (req, res) => {
19
+ res.json({
20
+ userAgent: req.headers.get("user-agent"),
21
+ host: req.headers.get("host"),
22
+ });
23
+ });
24
+ ```
25
+
26
+ The `.headers.entries()` method returns an iterator for all header key/value pairs.
27
+
28
+ ```javascript
29
+ // List all header names and values.
30
+ for (const [name, value] of req.headers.entries()) {
31
+ console.log(`${name}: ${value}`);
32
+ }
33
+ ```
34
+
35
+ If you need only the keys or values, use [`*.headers.keys()`](https://developer.mozilla.org/en-US/docs/Web/API/Headers/keys) or [`*.headers.values()`](https://developer.mozilla.org/en-US/docs/Web/API/Headers/values), respectively.
36
+
37
+ ## Check if set
38
+
39
+ If you need to check whether a header is set, use `*.headers.has(name)`:
40
+
41
+ ```javascript
42
+ router.use((req, res) => {
43
+ if (!req.headers.has("x-api-key")) {
44
+ res.sendStatus(403);
45
+ }
46
+ });
47
+ ```
48
+
49
+ ## Set headers
50
+
51
+ Set request or response headers by calling `*.headers.set(key, value)`.
52
+
53
+ ```javascript
54
+ router.get("/", (req, res) => {
55
+ res.headers.set("content-type", "text/html");
56
+ res.send("<html><body><h1>This is HTML!</h1></body></html>");
57
+ });
58
+ ```
59
+
60
+ ## Append headers
61
+
62
+ Append to request or response headers by calling `*.headers.append(key, value)`.
63
+
64
+ ```javascript
65
+ router.get("/", (req, res) => {
66
+ res.headers.append("set-cookie", "auth=token");
67
+ res.headers.append("set-cookie", "user=me");
68
+ res.send("Hello world!");
69
+ });
70
+ ```
71
+
72
+ ## Remove headers
73
+
74
+ Remove request or response headers by calling `*.headers.delete(key)`.
75
+
76
+ ```javascript
77
+ router.get("/", (req, res) => {
78
+ req.headers.delete("x-api-key");
79
+ // ...
80
+ });
81
+ ```
82
+
83
+ ## Aliases
84
+
85
+ ### req/res.set
86
+
87
+ Aliased helpers to set HTTP header values. To set multiple fields at once, pass an object as the only parameter.
88
+
89
+ ```javascript
90
+ res.set("content-type", "text/plain");
91
+
92
+ req.set({
93
+ "x-api-key": "my-api-key",
94
+ "x-debug": "1",
95
+ "host": "example.com"
96
+ });
97
+ ```
98
+
99
+ ### req/res.append
100
+
101
+ Aliased helpers to append HTTP header values. To set multiple fields at once, pass an object as the only parameter. To append iteratively to a single header, pass an array of strings as its value.
102
+
103
+ ```javascript
104
+ res.append("link", ["<http://localhost/>", "<http://localhost:3000/>"]);
105
+
106
+ req.append({
107
+ "warning": "199 Miscellaneous warning",
108
+ "x-forwarded-for": "example.com"
109
+ });
110
+ ```
@@ -0,0 +1,82 @@
1
+ ---
2
+ sidebar_position: 1
3
+ title: Request
4
+ ---
5
+
6
+ # The Request object
7
+
8
+ ## Properties
9
+
10
+ ### req.url
11
+ The [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) object corresponding to the request URL.
12
+
13
+ ### req.query
14
+ Request [query string parameters](search-params.md).
15
+
16
+ ### req.params
17
+ An object containing properties mapped to any [named route "parameters"](../routing#path-parameters), if the [`extractRequestParameters`](../config.md#extractRequestParameters) configuration option is enabled.
18
+
19
+ ### req.cookies
20
+ A Map containing [request cookies](cookies.md#request-cookies), if the [`parseCookie`](../config.md#parseCookie) configuration option is enabled.
21
+
22
+ ### req.headers
23
+ Request [headers](headers.md).
24
+
25
+ ### req.path
26
+ The path part of the request URL.
27
+
28
+ ### req.ip
29
+ The remote IP address of the request
30
+
31
+ ### req.protocol
32
+ The request protocol string: either `http` or (for TLS requests) `https`.
33
+
34
+ ### req.secure
35
+ A boolean value that is `true` if a TLS connection is established.
36
+
37
+ ### req.subdomains
38
+ An array of subdomains in the domain name of the request.
39
+
40
+ ### req.hostname
41
+ The hostname derived from the `Host` HTTP header.
42
+
43
+ ## Working with the request body
44
+
45
+ **expressly** can parse the body of a **req**uest in multiple ways.
46
+
47
+ ### As plain text
48
+
49
+ ```javascript
50
+ router.post("/submit", async (req, res) => {
51
+ let body = await req.text();
52
+
53
+ res.send(`You posted: "${body}"`)
54
+ })
55
+ ```
56
+
57
+ ### As JSON
58
+
59
+ ```javascript
60
+ router.post("/submit", async (req, res) => {
61
+ // Parse body as JSON
62
+ let body = await req.json();
63
+
64
+ // Check if the body contains the key "item"
65
+ if ("item" in body) {
66
+ res.send(`item: ${body.item}`)
67
+ } else {
68
+ res.send("You must include item in your body!")
69
+ }
70
+ })
71
+ ```
72
+
73
+ ### As an ArrayBuffer
74
+
75
+ ```javascript
76
+ router.post("/submit", async (req, res) => {
77
+ // Parse body into an ArrayBuffer
78
+ let body = await req.arrayBuffer();
79
+
80
+ console.debug(body);
81
+ })
82
+ ```