@fastly/expressly 1.0.0-alpha.1 → 1.0.1-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 (61) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
  2. package/.github/workflows/docs.yml +52 -0
  3. package/.github/workflows/release.yml +39 -0
  4. package/.husky/pre-commit +4 -0
  5. package/CHANGELOG.md +9 -0
  6. package/README.md +1 -0
  7. package/docs/.nvmrc +1 -0
  8. package/docs/README.md +27 -0
  9. package/docs/babel.config.js +3 -0
  10. package/docs/docs/config.md +52 -0
  11. package/docs/docs/handling-data/_category_.json +4 -0
  12. package/docs/docs/handling-data/cookies.md +103 -0
  13. package/docs/docs/handling-data/headers.md +110 -0
  14. package/docs/docs/handling-data/request.md +82 -0
  15. package/docs/docs/handling-data/response.md +162 -0
  16. package/docs/docs/handling-data/search-params.md +45 -0
  17. package/docs/docs/intro.md +61 -0
  18. package/docs/docs/middleware/_category_.json +5 -0
  19. package/docs/docs/middleware/controlling-flow.md +41 -0
  20. package/docs/docs/middleware/error-middleware.md +41 -0
  21. package/docs/docs/middleware/what-is-middleware.md +31 -0
  22. package/docs/docs/origin-data/_category_.json +4 -0
  23. package/docs/docs/origin-data/working-with-origins.md +49 -0
  24. package/docs/docs/routing.md +163 -0
  25. package/docs/docusaurus.config.js +119 -0
  26. package/docs/package.json +48 -0
  27. package/docs/sidebars.js +31 -0
  28. package/docs/src/components/HomepageFeatures.module.css +11 -0
  29. package/docs/src/components/HomepageFeatures.tsx +62 -0
  30. package/docs/src/css/custom.css +45 -0
  31. package/docs/src/pages/index.module.css +35 -0
  32. package/docs/src/pages/index.tsx +109 -0
  33. package/docs/static/.nojekyll +0 -0
  34. package/docs/static/img/favicon.ico +0 -0
  35. package/docs/static/img/logo-black.png +0 -0
  36. package/docs/static/img/logo-transparent.png +0 -0
  37. package/docs/static/img/logo.png +0 -0
  38. package/docs/static-host/Cargo.lock +435 -0
  39. package/docs/static-host/Cargo.toml +16 -0
  40. package/docs/static-host/fastly.toml +9 -0
  41. package/docs/static-host/rust-toolchain +3 -0
  42. package/docs/static-host/src/main.rs +115 -0
  43. package/docs/tsconfig.json +7 -0
  44. package/docs/yarn.lock +8416 -0
  45. package/package.json +1 -1
  46. package/src/index.ts +11 -0
  47. package/src/lib/routing/common.ts +34 -0
  48. package/src/lib/routing/error-middleware.ts +23 -0
  49. package/src/lib/routing/errors.ts +18 -0
  50. package/src/lib/routing/index.d.ts +18 -0
  51. package/src/lib/routing/request/cookie-map.ts +42 -0
  52. package/src/lib/routing/request/index.ts +64 -0
  53. package/src/lib/routing/request-handler.ts +22 -0
  54. package/src/lib/routing/response/index.ts +113 -0
  55. package/src/lib/routing/response/status-codes.ts +57 -0
  56. package/src/lib/routing/response/surrogate-keys.ts +32 -0
  57. package/src/lib/routing/router.ts +191 -0
  58. package/dist/lib/routing/middleware.d.ts +0 -10
  59. package/dist/lib/routing/middleware.js +0 -13
  60. package/dist/lib/routing/route.d.ts +0 -10
  61. package/dist/lib/routing/route.js +0 -12
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: Bug report
3
+ about: Tell us about a bug to help us improve
4
+ title: ''
5
+ labels: bug
6
+ assignees: doramatadora
7
+
8
+ ---
9
+
10
+ **Version**
11
+
12
+ Please provide the version of `expressly` that you're using.
13
+
14
+ **What happened**
15
+
16
+ Please describe what you did, what you expected to happen, and what happened instead.
@@ -0,0 +1,52 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ concurrency:
9
+ group: ${{ github.ref_name }}-docs
10
+ cancel-in-progress: true
11
+
12
+ jobs:
13
+ publish-docs:
14
+ name: Publish
15
+ runs-on: ubuntu-latest
16
+ defaults:
17
+ run:
18
+ working-directory: ./docs
19
+ steps:
20
+ - name: Checkout code
21
+ uses: actions/checkout@v2
22
+ - uses: actions/setup-node@v2
23
+ with:
24
+ node-version: 16
25
+ cache: yarn
26
+ - name: Install dependencies
27
+ run: yarn install --frozen-lockfile --prefer-offline
28
+ - name: Node modules cache
29
+ id: node-modules-cache
30
+ uses: actions/cache@v2
31
+ with:
32
+ path: node_modules
33
+ key: ${{ runner.os }}-expressly-docs-${{ secrets.cache_key_epoch_time }}-${{ hashFiles('yarn.lock') }}
34
+ - name: Compile docs
35
+ run: yarn build
36
+ - name: Install Rust toolchain
37
+ uses: actions-rs/toolchain@v1
38
+ with:
39
+ toolchain: stable
40
+ target: wasm32-wasi
41
+ - name: Deploy docs to Compute@Edge
42
+ uses: fastly/compute-actions@main
43
+ with:
44
+ project_directory: ./docs/static-host
45
+ env:
46
+ FASTLY_API_TOKEN: ${{ secrets.FASTLY_API_TOKEN }}
47
+ - name: Purge expressly.edgecompute.app
48
+ env:
49
+ FASTLY_API_TOKEN: ${{ secrets.FASTLY_API_TOKEN }}
50
+ run: |
51
+ curl -s -H "Fastly-Key: $FASTLY_API_TOKEN" -X POST "https://api.fastly.com/service/44O0OSuWrOWkzVek5Gg4QN/purge_all"
52
+
@@ -0,0 +1,39 @@
1
+ name: CI
2
+
3
+ on:
4
+ - workflow_dispatch
5
+ - pull_request
6
+
7
+ concurrency:
8
+ group: ${{ github.ref_name }}-expressly
9
+ cancel-in-progress: true
10
+
11
+ jobs:
12
+ build:
13
+ name: expressly
14
+ runs-on: ubuntu-latest
15
+ env:
16
+ GH_TOKEN: ${{ secrets.AUTO_GH_TOKEN }}
17
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
18
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
19
+ steps:
20
+ - name: Checkout code
21
+ uses: actions/checkout@v2
22
+ - uses: actions/setup-node@v2
23
+ with:
24
+ node-version: 16
25
+ cache: yarn
26
+ - name: Install dependencies
27
+ run: yarn install --frozen-lockfile --prefer-offline
28
+ - name: Node modules cache
29
+ id: node-modules-cache
30
+ uses: actions/cache@v2
31
+ with:
32
+ path: node_modules
33
+ key: ${{ runner.os }}-expressly-${{ secrets.cache_key_epoch_time }}-${{ hashFiles('yarn.lock') }}
34
+ - name: Compile expressly
35
+ run: yarn build
36
+ - name: Publish
37
+ run: |
38
+ git fetch origin 'refs/tags/*:refs/tags/*'
39
+ yarn auto shipit
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ . "$(dirname "$0")/_/husky.sh"
3
+
4
+ yarn pretty-quick --staged
package/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # v1.0.0 (Wed Jun 08 2022)
2
+
3
+ #### ⚠️ Pushed to `main`
4
+
5
+ - update auto config ([@doramatadora](https://github.com/doramatadora))
6
+
7
+ #### Authors: 1
8
+
9
+ - Dora Militaru ([@doramatadora](https://github.com/doramatadora))
package/README.md CHANGED
@@ -53,3 +53,4 @@ This will start your service on [http://localhost:7676](http://localhost:7676).
53
53
  ## Examples
54
54
 
55
55
  Check out the JavaScript code examples for Compute@Edge on Fastly's [Developer Hub](https://developer.fastly.com/solutions/examples/javascript/).
56
+ ß
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
+ ```
@@ -0,0 +1,162 @@
1
+ ---
2
+ sidebar_position: 2
3
+ ---
4
+
5
+ # Response
6
+
7
+ The **res**ponse object is the primary way to send data back to the client. **expressly** provides methods to set the response status, headers, and body.
8
+
9
+ ## res.send()
10
+
11
+ The easiest way to deliver a response is to use the **res.send()** method. This method takes a single argument, which is the response body.
12
+ The response body can be:
13
+
14
+ * A `string`,
15
+
16
+ ```javascript
17
+ router.get('/', (req, res) => {
18
+ res.send('Hello World!');
19
+ });
20
+ ```
21
+
22
+ * A `ReadableStream`,
23
+
24
+ ```javascript
25
+ router.get("/api", async (req, res) => {
26
+ let originRequest = await fetch(
27
+ "https://example.com", { backend: "my-origin" }
28
+ );
29
+
30
+ res.send(res.body);
31
+ });
32
+ ```
33
+
34
+ * Or a `Response`:
35
+
36
+ ```javascript
37
+ router.get("/origin", async (req, res) => {
38
+ res.send(await fetch(
39
+ "https://example.com", { backend: "my-origin" }
40
+ ));
41
+ });
42
+ ```
43
+ > 💡 If the _experimental_ [`autoContentType`](../config.md#parseCookie) configuration option is enabled, `res.send` 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.
44
+
45
+ ## res.end()
46
+
47
+ Use to quickly end the response without any data. If you need to respond with data, instead use methods such as [`res.send()`](#ressend) or [`res.json()`](#resjson).
48
+
49
+ ```javascript
50
+ router.get('/', (req, res) => {
51
+ res.end();
52
+ });
53
+ ```
54
+
55
+ ## res.sendStatus()
56
+
57
+ Sets the response HTTP status code to statusCode and sends the registered status message as the text response body.
58
+
59
+ ```javascript
60
+ router.get('/', (req, res) => {
61
+ res.sendStatus(403); // "Forbidden"
62
+ });
63
+ ```
64
+
65
+ ## res.redirect()
66
+
67
+ When you want to redirect the user to another page, you can use the `res.redirect()` method. It will set a redirect status code and set the `Location` header to the URL you want to redirect to.
68
+
69
+ ```javascript
70
+ router.get('/', function(req, res) {
71
+ res.redirect('/about');
72
+ });
73
+ ```
74
+ > 💡 This method accepts a second, optional argument – the redirect status code (defaults to `302`).
75
+
76
+ ```javascript
77
+ router.get("/redirect", async (req, res) => {
78
+ res.redirect("https://fastly.com", 307);
79
+ });
80
+ ```
81
+
82
+ ## res.json()
83
+
84
+ To return a serialized JSON response (`Content-Type: application/json`):
85
+
86
+ ```javascript
87
+ router.get('/', function(req, res) {
88
+ res.json({
89
+ message: 'Hello world!'
90
+ });
91
+ });
92
+ ```
93
+
94
+ ## res.text()
95
+
96
+ To return a plain-text response (`Content-Type: text/plain`):
97
+
98
+ ```javascript
99
+ router.get("/api", async (req, res) => {
100
+ res.text("Hello world!");
101
+ });
102
+ ```
103
+
104
+ ## res.html()
105
+
106
+ To return a HTML response (`Content-Type: text/html[; charset=my-charset]`):
107
+
108
+ ```javascript
109
+ router.get("/page", async (req, res) => {
110
+ // Takes a second, optional argument specifying a charset:
111
+ res.html("<html><body><h1>This is HTML!</h1></body></html>", "utf-8");
112
+ });
113
+ ```
114
+
115
+ ## res.withStatus()
116
+
117
+ **expressly** provides a chainable method to set the status code of a response:
118
+
119
+ ```javascript
120
+ router.get("/page", async (req, res) => {
121
+ res
122
+ .withStatus(404)
123
+ .html("<html><body><h1>Page not found</h1></body></html>");
124
+ });
125
+ ```
126
+
127
+
128
+ ## Response headers
129
+
130
+ [Read more](../handling-data/headers.md) about manipulating response (and request) headers with **expressly**.
131
+
132
+
133
+ ## Cookies
134
+
135
+ [Read how to](../handling-data/cookies.md#response-cookies) set and expire response cookies.
136
+
137
+ ## Cache segmentation
138
+
139
+ ### res.vary()
140
+
141
+ Appends a field to the `Vary` response header.
142
+
143
+ ```javascript
144
+ res.vary("x-feature-flags");
145
+ ```
146
+
147
+ ### res.surrogateKeys
148
+
149
+ **expressly** exposes `res.surrogateKeys`, a [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) object that allows you to manipulate the [`Surrogate-Key` header](https://developer.fastly.com/reference/http/http-headers/Surrogate-Key/).
150
+
151
+
152
+ ```javascript
153
+ res.surrogateKeys.add("seasonal");
154
+ res.surrogateKeys.add("vegan");
155
+ res.surrogateKeys.delete("low-carb");
156
+
157
+ // List all surrogate keys
158
+ for (const key of res.surrogateKeys) {
159
+ console.log(key);
160
+ }
161
+ ```
162
+