Skip to content

Commit 830cbcd

Browse files
committed
docs: add product description and update examples
1 parent f6720cb commit 830cbcd

File tree

1 file changed

+201
-4
lines changed

1 file changed

+201
-4
lines changed

README.md

Lines changed: 201 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,77 @@
22

33
Chainable and immutable HTTP handler for standard `Request` and `Response`.
44

5+
[![deno land](http://img.shields.io/badge/available%20on-deno.land/x-lightgrey.svg?logo=deno)](https://deno.land/x/chain_handler)
6+
[![deno doc](https://doc.deno.land/badge.svg)](https://doc.deno.land/https/deno.land/x/chain_handler/mod.ts)
7+
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/httpland/chain-handler)](https://github.com/httpland/chain-handler/releases)
8+
[![codecov](https://codecov.io/gh/httpland/chain-handler/branch/main/graph/badge.svg?token=nan4NUrx1V)](https://codecov.io/gh/httpland/chain-handler)
9+
[![GitHub](https://img.shields.io/github/license/httpland/chain-handler)](https://github.com/httpland/chain-handler/blob/main/LICENSE)
10+
11+
[![test](https://github.com/httpland/chain-handler/actions/workflows/test.yaml/badge.svg)](https://github.com/httpland/chain-handler/actions/workflows/test.yaml)
12+
[![NPM](https://nodei.co/npm/@httpland/chain-handler.png?mini=true)](https://nodei.co/npm/@httpland/chain-handler/)
13+
14+
## What
15+
16+
Defines an API for chainable HTTP handler.
17+
18+
```ts
19+
interface Handler {
20+
(request: Request): Response | Promise<Response>;
21+
}
22+
```
23+
24+
The declarative, web-standard compliant HTTP handler API is a powerful employed
25+
by `deno/std`.
26+
27+
We maintain this API and extend it. What we are adding to the `Handler` is a
28+
chaining mechanism.
29+
30+
```ts
31+
interface ChainableHandler {
32+
(request: Request, next: OptionalHandler): Response | Promise<Response>;
33+
}
34+
35+
interface OptionalHandler {
36+
(request?: Request): Response | Promise<Response>;
37+
}
38+
```
39+
40+
`ChainableHandler` is a handler that takes the next handler as the second
41+
argument.
42+
43+
`ChainableHandler` satisfies the following features:
44+
45+
- It can access to the `Request`.
46+
- It can access the next handler.
47+
- It can call the next handler.
48+
- It can choose not to call the next handler.
49+
- It can access the next handler's return value (`Response`).
50+
- It can return `Response`.
51+
52+
`OptionalHandler` is the next handler itself. It is optional to call it, or to
53+
pass a modified `Request` object.
54+
55+
However, because of the emphasis on immutable, the `Request` object is
56+
propagated only through its arguments.
57+
58+
These features make it the core of **middleware**.
59+
60+
## Packages
61+
62+
The package supports multiple platforms.
63+
64+
- deno.land/x - `https://deno.land/x/chain_handler/mod.ts`
65+
- npm - `@httpland/chain-handler`
66+
567
## Usage
668

69+
`Chain` is a stateful constructor. Add a `ChainableHandler` from the constructor
70+
or from the `next` function.
71+
72+
Handlers are executed asynchronously and recursively in the order of their
73+
declarations. Calling the next handler executes the next handler. `await` a call
74+
to the next handler gives access to the `Response` of the next handler.
75+
776
```ts
877
import { Chain } from "https://deno.land/x/chain_handler@$VERSION/mod.ts";
978
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
@@ -15,27 +84,155 @@ chain.next(async (request, next) => {
1584
const response = await next();
1685
console.log("end");
1786
return response;
18-
}).next((request, next) => {
87+
}, (request, next) => {
1988
// request proxy
2089
request.headers.append("x-proxy", "chain");
2190
return next(request);
22-
}).next(async (_, next) => {
91+
}, async (_, next) => {
2392
// response proxy
2493
const response = await next();
2594
response.headers.append("server", "deno");
2695
return response;
27-
}).next(() => {
96+
}, () => {
2897
// cut off chain
2998
return new Response("hello");
30-
}).next(() => {
99+
}, () => {
31100
// not call because cut off by previous chain.
32101
return new Response("goodby");
33102
});
103+
34104
const response = await chain.respond(new Request("http://localhost"));
35105
assertEquals(await response.text(), "hello");
36106
assertEquals(response.headers.get("server"), "deno");
37107
```
38108

109+
In the `respond` function, apply a `ChainableHandler` to convert the `Request`
110+
into a `Response`.
111+
112+
## Immutability
113+
114+
To reduce unexpected bugs, `Request` and `Response` are **NOT** shared among
115+
handlers. To propagate a change, pass the `Request` or `Response` to the next
116+
handler.
117+
118+
```ts
119+
import { Chain } from "https://deno.land/x/chain_handler@$VERSION/mod.ts";
120+
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
121+
122+
const chain = new Chain();
123+
124+
chain.next((request, next) => {
125+
request.headers.append("x-effect", "no effect");
126+
return next();
127+
}).next((request, next) => {
128+
assertEquals(request.headers.get("x-effect"), null);
129+
130+
return next();
131+
});
132+
```
133+
134+
In order to propagate changes, a `Request` or `Response` must be passed to the
135+
next handler.
136+
137+
```ts
138+
import { Chain } from "https://deno.land/x/chain_handler@$VERSION/mod.ts";
139+
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
140+
141+
const chain = new Chain((request, next) => {
142+
request.headers.append("x-effect", "effected");
143+
144+
return next(request);
145+
}, (request) => {
146+
assertEquals(request.headers.get("x-effect"), "effected");
147+
148+
return new Response("ok");
149+
});
150+
```
151+
152+
This ensures that there are no destructive changes to the object.
153+
154+
## Utility
155+
156+
Stateless functions are also available.
157+
158+
```ts
159+
import {
160+
chain,
161+
type ChainableHandler,
162+
} from "https://deno.land/x/chain_handler@$VERSION/mod.ts";
163+
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
164+
165+
const initRequest = new Request("http://localhost");
166+
const initResponse = new Response(null, { status: 404 });
167+
const justThrough: ChainableHandler = (_, next) => next();
168+
169+
const response = await chain(
170+
initRequest,
171+
initResponse,
172+
justThrough,
173+
justThrough,
174+
...new Array(5).fill(justThrough),
175+
);
176+
assertEquals(response.status, 404);
177+
```
178+
179+
## Tips
180+
181+
Detailed specifications are explained below.
182+
183+
### Clone is not required
184+
185+
If you make a destructive change, such as reading a body, you do not need to
186+
clone it for the next handler.
187+
188+
```ts
189+
import { Chain } from "https://deno.land/x/chain_handler@$VERSION/mod.ts";
190+
191+
const chain = new Chain(async (request, next) => {
192+
// No need request.clone()
193+
const text = await request.text();
194+
return next();
195+
}, async (request) => {
196+
const json = await request.json();
197+
return new Response("ok");
198+
});
199+
```
200+
201+
A clone of the **argument** or **return value** object is always used.
202+
203+
That is, `clone` is required in the following cases
204+
205+
```ts
206+
import { Chain } from "https://deno.land/x/chain_handler@$VERSION/mod.ts";
207+
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
208+
209+
const chain = new Chain(async (request, next) => {
210+
const response = await next();
211+
const cloned = response.clone();
212+
213+
assertEquals(await cloned.text(), "ok");
214+
215+
return response;
216+
}, async () => new Response("ok"));
217+
```
218+
219+
### Default response
220+
221+
Default Response is `new Response(null, { status: 404 })`. This can be
222+
completely changed.
223+
224+
```ts
225+
import { Chain } from "https://deno.land/x/chain_handler@$VERSION/mod.ts";
226+
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
227+
228+
const response = await new Chain().respond(
229+
new Request("http://localhost"),
230+
new Response("ok"),
231+
);
232+
233+
assertEquals(await response.text(), "ok");
234+
```
235+
39236
## License
40237

41238
Copyright © 2023-present [httpland](https://github.com/httpland).

0 commit comments

Comments
 (0)