@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.
- package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
- package/.github/workflows/docs.yml +52 -0
- package/.github/workflows/release.yml +39 -0
- package/.husky/pre-commit +4 -0
- package/CHANGELOG.md +9 -0
- package/README.md +1 -0
- package/docs/.nvmrc +1 -0
- package/docs/README.md +27 -0
- package/docs/babel.config.js +3 -0
- package/docs/docs/config.md +52 -0
- package/docs/docs/handling-data/_category_.json +4 -0
- package/docs/docs/handling-data/cookies.md +103 -0
- package/docs/docs/handling-data/headers.md +110 -0
- package/docs/docs/handling-data/request.md +82 -0
- package/docs/docs/handling-data/response.md +162 -0
- package/docs/docs/handling-data/search-params.md +45 -0
- package/docs/docs/intro.md +61 -0
- package/docs/docs/middleware/_category_.json +5 -0
- package/docs/docs/middleware/controlling-flow.md +41 -0
- package/docs/docs/middleware/error-middleware.md +41 -0
- package/docs/docs/middleware/what-is-middleware.md +31 -0
- package/docs/docs/origin-data/_category_.json +4 -0
- package/docs/docs/origin-data/working-with-origins.md +49 -0
- package/docs/docs/routing.md +163 -0
- package/docs/docusaurus.config.js +119 -0
- package/docs/package.json +48 -0
- package/docs/sidebars.js +31 -0
- package/docs/src/components/HomepageFeatures.module.css +11 -0
- package/docs/src/components/HomepageFeatures.tsx +62 -0
- package/docs/src/css/custom.css +45 -0
- package/docs/src/pages/index.module.css +35 -0
- package/docs/src/pages/index.tsx +109 -0
- package/docs/static/.nojekyll +0 -0
- package/docs/static/img/favicon.ico +0 -0
- package/docs/static/img/logo-black.png +0 -0
- package/docs/static/img/logo-transparent.png +0 -0
- package/docs/static/img/logo.png +0 -0
- package/docs/static-host/Cargo.lock +435 -0
- package/docs/static-host/Cargo.toml +16 -0
- package/docs/static-host/fastly.toml +9 -0
- package/docs/static-host/rust-toolchain +3 -0
- package/docs/static-host/src/main.rs +115 -0
- package/docs/tsconfig.json +7 -0
- package/docs/yarn.lock +8416 -0
- package/package.json +1 -1
- package/src/index.ts +11 -0
- package/src/lib/routing/common.ts +34 -0
- package/src/lib/routing/error-middleware.ts +23 -0
- package/src/lib/routing/errors.ts +18 -0
- package/src/lib/routing/index.d.ts +18 -0
- package/src/lib/routing/request/cookie-map.ts +42 -0
- package/src/lib/routing/request/index.ts +64 -0
- package/src/lib/routing/request-handler.ts +22 -0
- package/src/lib/routing/response/index.ts +113 -0
- package/src/lib/routing/response/status-codes.ts +57 -0
- package/src/lib/routing/response/surrogate-keys.ts +32 -0
- package/src/lib/routing/router.ts +191 -0
- package/dist/lib/routing/middleware.d.ts +0 -10
- package/dist/lib/routing/middleware.js +0 -13
- package/dist/lib/routing/route.d.ts +0 -10
- 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
|
package/CHANGELOG.md
ADDED
package/README.md
CHANGED
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,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,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
|
+
|