Skip to content

Commit 97635a8

Browse files
Add Readme
1 parent 7f9a5e9 commit 97635a8

File tree

17 files changed

+493
-73
lines changed

17 files changed

+493
-73
lines changed

README.md

Lines changed: 242 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,242 @@
1-
# roarter
1+
# Roarter
2+
3+
A minimalist, intuitive, and unobtrusive middleware framework for Deno.
4+
5+
The goal of Roarter is to only add what is absolutely necessary to the native
6+
HTTP server API, with as few abstractions as we can get away with.
7+
8+
# Handlers
9+
10+
The `Application` class is responsible for coordinating an HTTP request's
11+
interaction with handlers. You may register a new `Handler` via
12+
`Application.handle()` as shown below.
13+
14+
```typescript
15+
let app = new Application();
16+
17+
app
18+
.handle(async (req) => {
19+
return Response.text("Hello World");
20+
});
21+
22+
await app.serve({ port: 8080 });
23+
```
24+
25+
If you run the script
26+
27+
```shell
28+
deno run --allow-net helloWorld.ts
29+
```
30+
31+
all requests made to `localhost:8080` will return `Hello World`. This is because
32+
we have not specified any `Matchers` for our `Handler`, so it will match all/any
33+
incoming requests.
34+
35+
> NOTE: Handlers must be async (return a promise).
36+
>
37+
> This is to allow Roarter to orchestrate the HTTP request among middlewares and
38+
> handlers.
39+
40+
# Matchers
41+
42+
By adding `Matchers` we can make it so the `Handler` is only executed if the
43+
given conditions are met. For example, if we want our above example to only
44+
respond `Hello World` if the request has an HTTP verb of `GET`, we would do the
45+
following:
46+
47+
```typescript
48+
let app = new Application();
49+
50+
app
51+
.match((req) => req.method === "GET")
52+
.handle(async (req) => {
53+
return Response.text("Hello World");
54+
});
55+
56+
await app.serve({ port: 8080 });
57+
```
58+
59+
`Matchers` are passed the `Request` object and are expected to return boolean.
60+
If all matchers return `true`, then `Application` will run the `Handler`.
61+
62+
In practice, Roarter includes most of the matchers you would ever need, so the
63+
above example may be written with the `.get` matcher that's already included in
64+
the framework.
65+
66+
```typescript
67+
app
68+
.get
69+
.handle(async (req) => {
70+
return Response.text("Hello World");
71+
});
72+
```
73+
74+
> Matchers may also pass information down to their handler which may be accessed
75+
> via `req.params` or `req.vars`.
76+
>
77+
> This is useful when creating matchers that must "capture" values and pass them
78+
> down. An example of this is the `.path("/user/:userId")` matcher which has to
79+
> capture the value passed into `:userId`.
80+
81+
# Params
82+
83+
The `path()` matcher can be used to match a pathname and, optionally, capture
84+
params. It stores all path params under the `req.params`
85+
[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).
86+
87+
```typescript
88+
app
89+
.get
90+
.path("/user/:userId")
91+
.handle(async (req) => {
92+
return new Response("Echoing param " + req.params.get("userId"));
93+
});
94+
```
95+
96+
# Sub Applications
97+
98+
As your application gets larger you will want to logically organize your
99+
handlers. Roarter supports sub-routing to meet this need. Simply pass an
100+
instance of `Application` to the `.handle()` method and it will treat it as a
101+
sub-router.
102+
103+
```typescript
104+
let user = new Application();
105+
106+
/**
107+
* GET tenant/:tenantId/user/:userId
108+
*/
109+
user
110+
.get
111+
.path("/user/:userId")
112+
.handle(async (req) => {
113+
return new Response(
114+
`Echoing params ${req.params.get("tenantId")}, ${
115+
req.params.get("userId")
116+
}`,
117+
);
118+
});
119+
120+
let tenant = new Application();
121+
122+
/**
123+
* GET tenant/:tenantId
124+
*/
125+
tenant
126+
.path("/tenant/:tenantId")
127+
.handle(user);
128+
129+
await tenant.serve({ port: 8080 });
130+
```
131+
132+
# Errors
133+
134+
Roarter `Applications` can handle errors within the handler itself:
135+
136+
```typescript
137+
app
138+
.post
139+
.path("/json")
140+
.handle(async (req) => {
141+
if (!req.body) {
142+
return Response.text("no body", { status: 400 });
143+
}
144+
});
145+
```
146+
147+
Or via `Application.catch()`:
148+
149+
```typescript
150+
app.catch(async (req, err) => {
151+
return Response.text(err.message, { status: 500 });
152+
});
153+
```
154+
155+
> `.catch()` gets called if any handlers throw an exception. It is always a good
156+
> idea to add one in production.
157+
158+
# The Request and Response Objects
159+
160+
Roarter's `Request` and `Response` objects extend the native
161+
[Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) and
162+
[Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects.
163+
Our goal is for our users to familiarize themselves with de native api as much
164+
as possible. That said, we are not against adding practical utility methods when
165+
needed. Below is a practical example of receiving and responding JSON.
166+
167+
```typescript
168+
let app = new Application();
169+
170+
/**
171+
* GET /json
172+
*/
173+
app
174+
.get
175+
.path("/json")
176+
.handle(async (req) => {
177+
const response = {
178+
id: "123",
179+
data: [1, 2, 3],
180+
};
181+
182+
return Response.json(response);
183+
});
184+
185+
/**
186+
* POST /json
187+
*/
188+
app
189+
.post
190+
.path("/json")
191+
.handle(async (req) => {
192+
if (!req.body) {
193+
return Response.text("no body", { status: 400 });
194+
}
195+
196+
const body = await req.json();
197+
198+
return Response.json(body);
199+
});
200+
201+
app.catch(async (req, err) => {
202+
return Response.text(err.message, { status: 500 });
203+
});
204+
205+
await app.serve({ port: 8080 });
206+
```
207+
208+
# Middleware
209+
210+
A Handler with no Matchers is essentially a Middleware. Roarter will run all
211+
matching Handlers in order of insertion, so a Middleware that is added before a
212+
Handler will run first, the Handler will run after that, and, finally, the
213+
remaining Middleware if any.
214+
215+
```typescript
216+
let app = new Application();
217+
218+
const jsonParser = async (req: Request) => {
219+
if (req.body) {
220+
req.vars.set("body", await req.json());
221+
}
222+
};
223+
224+
const logger = async (req: Request) => {
225+
console.log(`Request sent to ${req.pathname}`);
226+
};
227+
228+
app
229+
.handle(jsonParser);
230+
231+
app
232+
.post
233+
.path("/json")
234+
.handle(async (req) => {
235+
return Response.json(req.vars.get("body"));
236+
});
237+
238+
app
239+
.handle(logger);
240+
241+
await app.serve({ port: 8080 });
242+
```

application.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { serve, ServeInit } from "https://deno.land/[email protected]/http/server.ts";
22
import { Handler } from "./handler.ts";
3-
import {CatchFunc, HandleFunc, Matcher} from "./types.ts";
3+
import { CatchFunc, HandleFunc, Matcher } from "./types.ts";
44
import { Request as RoarterRequest } from "./request.ts";
55

66
export class Application {
7-
handlers: Handler[] = [];
8-
catchFunc: CatchFunc = () => {
7+
private handlers: Handler[] = [];
8+
private catchFunc: CatchFunc = () => {
99
throw new Error("Not Implemented");
1010
};
1111

@@ -63,30 +63,30 @@ export class Application {
6363
return h;
6464
}
6565

66-
catch(fn: CatchFunc) {
66+
catch(fn: CatchFunc): void {
6767
this.catchFunc = fn;
6868
}
6969

7070
// Handler runs all the registered handlers and returns the first
7171
// response it finds. After returning the first response, it runs
7272
// the remaining handlers. The purpose of this is to support middleware
7373
// after a response has been sent.
74-
async runHandlers(req: RoarterRequest): Promise<Response> {
75-
let runRemaining = true
74+
private async runHandlers(req: RoarterRequest): Promise<Response> {
75+
let runRemaining = true;
7676
let i = 0;
7777
try {
7878
try {
7979
for (i; i < this.handlers.length; i++) {
8080
const handler = this.handlers[i];
81-
const response = await handler.run(req);
81+
const response = await handler["run"](req);
8282
if (response) {
8383
i++;
8484
return response;
8585
}
8686
}
8787
} catch (e) {
8888
// If an error occurs, we wan't to skip all handlers and run the catchFunc
89-
runRemaining = false
89+
runRemaining = false;
9090
return await this.catchFunc(req, e);
9191
}
9292
// If after running all the handlers there is no response, throw the error
@@ -99,17 +99,17 @@ export class Application {
9999
// we run the remaining handlers ignoring their response and errors
100100
for (i; i < this.handlers.length; i++) {
101101
const handler = this.handlers[i];
102-
handler.run(req).catch(e => {
103-
this.catchFunc(req, e)
104-
})
102+
handler["run"](req).catch((e) => {
103+
this.catchFunc(req, e);
104+
});
105105
}
106106
}
107107
}
108108
}
109109

110-
async handler(domReq: Request): Promise<Response> {
110+
private async handler(domReq: Request): Promise<Response> {
111111
const req = new RoarterRequest(domReq);
112-
return this.runHandlers(req)
112+
return this.runHandlers(req);
113113
}
114114

115115
async serve(options?: ServeInit) {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Application, Response } from "../../mod.ts";
2+
3+
let app = new Application();
4+
5+
app
6+
.get
7+
.path("/")
8+
.handle(async (req) => {
9+
return Response.text("Hello World");
10+
});
11+
12+
await app.serve({ port: 8080 });

examples/json/json.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Application, Response } from "../../mod.ts";
2+
3+
let app = new Application();
4+
5+
/**
6+
* GET /json
7+
*/
8+
app
9+
.get
10+
.path("/json")
11+
.handle(async (req) => {
12+
const response = {
13+
id: "123",
14+
data: [1, 2, 3],
15+
};
16+
17+
return Response.json(response);
18+
});
19+
20+
/**
21+
* POST /json
22+
*/
23+
app
24+
.post
25+
.path("/json")
26+
.handle(async (req) => {
27+
if (!req.body) {
28+
return Response.text("no body", { status: 400 });
29+
}
30+
31+
const body = await req.json();
32+
33+
return Response.json(body);
34+
});
35+
36+
app.catch(async (req, err) => {
37+
return Response.text(err.message, { status: 500 });
38+
});
39+
40+
await app.serve({ port: 8080 });

examples/matchers/matchers.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Application, Response } from "../../mod.ts";
2+
3+
let app = new Application();
4+
5+
app
6+
.match((req) => req.method === "GET")
7+
.handle(async (req) => {
8+
return Response.text("Hello World");
9+
});
10+
11+
await app.serve({ port: 8080 });

0 commit comments

Comments
 (0)