Skip to content
This repository was archived by the owner on Jul 31, 2025. It is now read-only.

Commit 7bfa794

Browse files
author
v1rtl
committed
support res in handlers
1 parent 5e52689 commit 7bfa794

File tree

8 files changed

+116
-28
lines changed

8 files changed

+116
-28
lines changed

README.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
Deno port of [tinyhttp](https://github.com/talentlessguy/tinyhttp), 0-legacy, tiny & fast web framework as a replacement of Express.
66

7-
> **WARNING!** This port is very unstable and lacks features. It also doesn't have all of the tinyhttp's original extensions.
7+
> **WARNING!** This port is very unstable and lacks features. It also doesn't have all of the tinyhttp's original extensions. Wait for the v2 release of tinyhttp for a better version (see [talentlessguy/tinyhttp#198](https://github.com/talentlessguy/tinyhttp/issues/198))
88
99
## Example
1010

@@ -13,19 +13,17 @@ import { App } from 'https://deno.land/x/tinyhttp@v0.0.3/app.ts'
1313

1414
const app = new App()
1515

16-
app.use('/', (req, next) => {
16+
app.use('/', (req, res, next) => {
1717
console.log(`${req.method} ${req.url}`)
1818

19+
res.headers.set('Test-Header', 'Value')
20+
1921
next()
2022
})
2123

22-
app.get('/:name/', (req) => {
23-
req.respond({ body: `Hello ${req.params.name}!` })
24+
app.get('/:name/', (req, res) => {
25+
res.send(`Hello on ${req.url} from Deno and tinyhttp! 🦕`)
2426
})
2527

2628
app.listen(3000, () => console.log(`Started on :3000`))
2729
```
28-
29-
## Changes
30-
31-
Because Deno doesn't have the same API for HTTP server, there's no `res` argument. To send responses use `req.respond` instead.

app.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
// deno-lint-ignore-file
2-
import { NextFunction, Router, Handler, Middleware, UseMethodParams } from 'https://esm.sh/@tinyhttp/router'
2+
import { NextFunction, Router, Handler, Middleware, UseMethodParams } from 'https://esm.sh/@tinyhttp/router@1.2.1'
33
import { onErrorHandler, ErrorHandler } from './onError.ts'
44
import 'https://deno.land/std@0.87.0/node/global.ts'
55
import rg from 'https://esm.sh/regexparam'
66
import { Request } from './request.ts'
7-
import { getURLParams } from 'https://cdn.esm.sh/v15/@tinyhttp/url@1.1.2/esnext/url.js'
7+
import { Response } from './response.ts'
8+
import { getURLParams } from 'https://cdn.esm.sh/v15/@tinyhttp/url@1.2.0/esnext/url.js'
89
import { extendMiddleware } from './extend.ts'
910
import { serve, Server } from 'https://deno.land/std@0.87.0/http/server.ts'
1011
import { parse } from './parseUrl.ts'
1112

1213
const lead = (x: string) => (x.charCodeAt(0) === 47 ? x : '/' + x)
1314

14-
export const applyHandler = <Req>(h: Handler<Req>) => async (req: Req, next: NextFunction) => {
15+
export const applyHandler = <Req, Res>(h: Handler<Req, Res>) => async (req: Req, res: Res, next: NextFunction) => {
1516
try {
1617
if (h.constructor.name === 'AsyncFunction') {
17-
await h(req, next)
18-
} else h(req, next)
18+
await h(req, res, next)
19+
} else h(req, res, next)
1920
} catch (e) {
2021
next(e)
2122
}
@@ -47,21 +48,25 @@ export type TemplateEngineOptions<O = any> = Partial<{
4748
_locals: Record<string, any>
4849
}>
4950

50-
export class App<RenderOptions = any, Req extends Request = Request> extends Router<App, Req> {
51+
export class App<RenderOptions = any, Req extends Request = Request, Res extends Response = Response> extends Router<
52+
App,
53+
Req,
54+
Res
55+
> {
5156
middleware: Middleware<Req>[] = []
5257
locals: Record<string, string> = {}
5358
noMatchHandler: Handler
5459
onError: ErrorHandler
5560
settings: AppSettings
5661
engines: Record<string, TemplateFunc<RenderOptions>> = {}
57-
applyExtensions?: (req: Request, next: NextFunction) => void
62+
applyExtensions?: (req: Req, res: Res, next: NextFunction) => void
5863

5964
constructor(
6065
options: Partial<{
6166
noMatchHandler: Handler<Req>
6267
onError: ErrorHandler
6368
settings: AppSettings
64-
applyExtensions: (req: Request, next: NextFunction) => void
69+
applyExtensions: (req: Req, res: Res, next: NextFunction) => void
6570
}> = {}
6671
) {
6772
super()
@@ -79,7 +84,7 @@ export class App<RenderOptions = any, Req extends Request = Request> extends Rou
7984
return app
8085
}
8186

82-
use(...args: UseMethodParams<Req, any, App>) {
87+
use(...args: UseMethodParams<Req, Res, App>) {
8388
const base = args[0]
8489

8590
const fns: any[] = args.slice(1)
@@ -128,13 +133,17 @@ export class App<RenderOptions = any, Req extends Request = Request> extends Rou
128133
const { xPoweredBy } = this.settings
129134
if (xPoweredBy) req.headers.set('X-Powered-By', typeof xPoweredBy === 'string' ? xPoweredBy : 'tinyhttp')
130135

131-
const exts = this.applyExtensions || extendMiddleware<RenderOptions>(this as any)
136+
let res = {
137+
headers: new Headers({})
138+
}
139+
140+
const exts = this.applyExtensions || extendMiddleware<RenderOptions, Req, Res>(this as any)
132141

133142
req.originalUrl = req.url || req.originalUrl
134143

135144
const { pathname } = parse(req.originalUrl)
136145

137-
const mw: Middleware[] = [
146+
const mw: Middleware<Req, Res>[] = [
138147
{
139148
handler: exts,
140149
type: 'mw',
@@ -148,7 +157,7 @@ export class App<RenderOptions = any, Req extends Request = Request> extends Rou
148157
}
149158
]
150159

151-
const handle = (mw: Middleware) => async (req: Req, next: NextFunction) => {
160+
const handle = (mw: Middleware<Req, Res>) => async (req: Req, res: Res, next: NextFunction) => {
152161
const { path = '/', handler, type, regex = rg('/') } = mw
153162

154163
req.url = lead(req.url.substring(path.length)) || '/'
@@ -157,14 +166,14 @@ export class App<RenderOptions = any, Req extends Request = Request> extends Rou
157166

158167
if (type === 'route') req.params = getURLParams(regex, pathname)
159168

160-
await applyHandler<Req>((handler as unknown) as Handler<Req>)(req, next)
169+
await applyHandler<Req, Res>((handler as unknown) as Handler<Req, Res>)(req, res, next)
161170
}
162171

163172
let idx = 0
164173

165-
next = next || ((err) => (err ? this.onError(err, req) : loop()))
174+
next = next || ((err: any) => (err ? this.onError(err, req) : loop()))
166175

167-
const loop = () => idx < mw.length && handle(mw[idx++])(req, next as NextFunction)
176+
const loop = () => idx < mw.length && handle(mw[idx++])(req, (res as unknown) as Res, next as NextFunction)
168177

169178
loop()
170179
}

example/mod.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import { App } from '../app.ts'
22

33
const app = new App()
44

5-
app.use('/', (req, next) => {
5+
app.use('/', (req, res, next) => {
66
console.log(`${req.method} ${req.url}`)
77

8+
res.headers.set('Test-Header', 'Value')
9+
810
next()
911
})
1012

11-
app.get('/:name/', (req) => {
12-
req.respond({ body: `Hello ${req.params.name}!` })
13+
app.get('/:name/', (req, res) => {
14+
res.send(`Hello on ${req.url} from Deno and tinyhttp! 🦕`)
1315
})
1416

1517
app.listen(3000, () => console.log(`Started on :3000`))

extend.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
11
import { NextFunction } from 'https://esm.sh/@tinyhttp/router'
22
import { App } from './app.ts'
33
import { Request } from './request.ts'
4+
import { getRequestHeader, getFreshOrStale } from './extensions/req/headers.ts'
5+
import { send } from './extensions/res/send.ts'
6+
import { Response } from './response.ts'
47

5-
export const extendMiddleware = <RenderOptions = unknown>(app: App) => (req: Request, next: NextFunction) => {
8+
export const extendMiddleware = <
9+
RenderOptions = unknown,
10+
Req extends Request = Request,
11+
Res extends Response = Response
12+
>(
13+
app: App
14+
) => (req: Req, res: Res, next?: NextFunction) => {
615
const { settings } = app
716

17+
// Request extensions
818
if (settings?.bindAppToReqRes) {
919
req.app = app
1020
}
11-
next()
21+
22+
req.get = getRequestHeader(req)
23+
24+
if (settings?.freshnessTesting) {
25+
req.fresh = getFreshOrStale(req, res)
26+
req.stale = !req.fresh
27+
}
28+
29+
res.send = send(req, res)
30+
31+
next?.()
1232
}

extensions/req/headers.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { ServerRequest, Response } from 'https://deno.land/std@0.87.0/http/server.ts'
2+
import parseRange, { Options } from 'https://esm.sh/range-parser'
3+
import fresh from 'https://deno.land/x/fresh/mod.ts'
4+
5+
export const getRequestHeader = (req: ServerRequest) => (header: string): string | string[] | null => {
6+
const lc = header.toLowerCase()
7+
8+
switch (lc) {
9+
case 'referer':
10+
case 'referrer':
11+
return req.headers.get('referrer') || req.headers.get('referer')
12+
default:
13+
return req.headers.get(lc)
14+
}
15+
}
16+
export const getRangeFromHeader = (req: ServerRequest) => (size: number, options?: Options) => {
17+
const range = getRequestHeader(req)('Range') as string
18+
19+
if (!range) return
20+
21+
return parseRange(size, range, options)
22+
}
23+
24+
export const getFreshOrStale = (req: ServerRequest, res: Response) => {
25+
const method = req.method
26+
const status = res.status || 200
27+
28+
// GET or HEAD for weak freshness validation only
29+
if (method !== 'GET' && method !== 'HEAD') return false
30+
31+
// 2xx or 304 as per rfc2616 14.26
32+
if ((status >= 200 && status < 300) || status === 304) {
33+
return fresh(
34+
req.headers,
35+
new Headers({
36+
etag: getRequestHeader(req)('ETag') as string,
37+
'last-modified': res.headers?.get('Last-Modified') as string
38+
})
39+
)
40+
}
41+
42+
return false
43+
}

extensions/res/send.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Request } from '../../request.ts'
2+
import { Response } from '../../response.ts'
3+
4+
export const send = (req: Request, res: Response) => (body: string) => {
5+
req.respond({ ...res, body })
6+
return res
7+
}

request.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,7 @@ export interface Request extends ServerRequest {
77
originalUrl: string
88
app: App
99
params: Record<string, any>
10+
get: (header: string) => string | string[] | null
11+
fresh?: boolean
12+
stale?: boolean
1013
}

response.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { Response as ServerResponse } from 'https://deno.land/std@0.87.0/http/server.ts'
2+
3+
export interface Response extends ServerResponse {
4+
headers: Headers
5+
send(body: string): Response
6+
}

0 commit comments

Comments
 (0)