Skip to content

Commit 1271e24

Browse files
v1rtlv1rtl
authored andcommitted
add cookie / header extensions to res and req, write cookie modules
1 parent a49bfd7 commit 1271e24

File tree

24 files changed

+720
-232
lines changed

24 files changed

+720
-232
lines changed

.npmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
link-workspace-packages = false
1+
link-workspace-packages = true
22
shamefully-hoist = true
33
shared-workspace-shrinkwrap = true

examples/basic/index.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import { App } from '../../packages/app/src/index'
2-
import staticFolder from '../../packages/static/index'
3-
import logger from '../../packages/logger/index'
2+
import staticFolder from '../../packages/static/src/index'
3+
import logger from '../../packages/logger/src/index'
44

55
const app = new App()
66

77
app.get('/', (_, res) => void res.send('<h1>Hello World</h1>'))
88

9-
app.all('/page/:page/', (req, res) => {
9+
app.all('*', (_, res) => {
10+
throw new Error('oops')
11+
})
12+
13+
app.get('/page/:page/', (req, res) => {
1014
res.status(200).send(`
1115
<h1>Some cool page</h1>
1216
<h2>URL</h2>
@@ -16,8 +20,6 @@ app.all('/page/:page/', (req, res) => {
1620
`)
1721
})
1822

19-
app.use(staticFolder())
20-
21-
app.use(logger())
23+
app.use(logger()).use(staticFolder())
2224

2325
app.listen(3000)

examples/basic/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"extends": "../../tsconfig.json",
33
"compilerOptions": {
4+
"module": "CommonJS",
45
"outDir": "dist"
56
}
67
}

packages/app/README.md

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,75 @@
1-
# @tinyhttp/app
1+
![Twitter](https://img.shields.io/twitter/follow/v1rtl.svg?label=sub%20to%20twitter&style=flat-square) ![npm type definitions](https://img.shields.io/npm/types/@tinyhttp/app?style=flat-square)
2+
![Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/npm/body-parsec.svg?style=flat-square)
3+
![Last commit](https://img.shields.io/github/last-commit/talentlessguy/tinyhttp.svg?style=flat-square) ![NPM](https://img.shields.io/npm/l/@tinyhttp/app?style=flat-square)
24

3-
tinyhttp core
5+
# @tinyhttp/core
6+
7+
`tinyhttp` core module with `App`, `Request` and `Response` classes.
8+
9+
> ⚠ The project is incomplete. Please don't use in production.
10+
11+
**tinyhttp** is a modern Express-like web framework for Node.js. It uses a bare minimum amount of dependencies trying to avoid legacy.
412

513
## Installation
614

7-
This is a [Node.js](https://nodejs.org/) module available through the
8-
[npm registry](https://www.npmjs.com/). It can be installed using the
9-
[`npm`](https://docs.npmjs.com/getting-started/installing-npm-packages-locally)
10-
or
11-
[`yarn`](https://yarnpkg.com/en/)
12-
command line tools.
15+
Node.js 13 is required.
1316

1417
```sh
15-
npm install @tinyhttp/app --save
18+
# npm
19+
npm i @tinyhttp/app
20+
# pnpm
21+
pnpm i @tinyhttp/app
22+
# yarn
23+
yarn add @tinyhttp/app
1624
```
1725

18-
## Dependencies
26+
## Features
27+
28+
- Compatible with Express
29+
- Async routes [not tested yet]
30+
- Smaller size
31+
- 0 legacy dependencies
32+
33+
## Docs
34+
35+
Coming soon...
1936

20-
- [@tinyhttp/etag](https://ghub.io/@tinyhttp/etag): tinyhttp eTag module
21-
- [content-type](https://ghub.io/content-type): Create and parse HTTP Content-Type header
22-
- [proxy-addr](https://ghub.io/proxy-addr): Determine address of proxied request
23-
- [range-parser](https://ghub.io/range-parser): Range header field string parser
24-
- [regexparam](https://ghub.io/regexparam): A tiny (308B) utility that converts route patterns into RegExp. Limited alternative to `path-to-regexp` 🙇‍
37+
## Example
2538

26-
## Dev Dependencies
39+
At the moment there is only one basic example. I will add more of them once I add all the existing Express `req` / `res` extensions.
2740

28-
- [@types/proxy-addr](https://ghub.io/@types/proxy-addr): TypeScript definitions for proxy-addr
29-
- [@types/range-parser](https://ghub.io/@types/range-parser): TypeScript definitions for range-parser
41+
```ts
42+
import { App } from '@tinyhttp/app'
43+
import staticFolder from '@tinyhttp/static'
44+
import logger from '@tinyhttp/logger'
3045

31-
## License
46+
const app = new App()
3247

33-
MIT
48+
app.all('/', (req, res) => {
49+
res.status(200).send(`
50+
<h1>tinyhttp example</h1>
51+
<ul>
52+
<li>Protocol: ${req.protocol}</li>
53+
<li>HTTPS: ${req.secure ? 'yes' : 'no'}</li>
54+
<li>URL: ${req.url}</li>
55+
<li>Method: ${req.method}</li>
56+
<li>Host: ${req.hostname}</li>
57+
<li>Status: ${res.statusCode}</li>
58+
</ul>
59+
<h2>Request headers</h2>
60+
<pre>
61+
${JSON.stringify(req.headers, null, 2)}
62+
</pre>
63+
`)
64+
})
65+
66+
app.get('/:first/:second', (req, res) => {
67+
res.json({ URLParams: req.params, QueryParams: req.query })
68+
})
69+
70+
app.use(staticFolder())
71+
72+
app.use(logger())
73+
74+
app.listen(3000)
75+
```

packages/app/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,19 @@
2828
"license": "MIT",
2929
"dependencies": {
3030
"@tinyhttp/cookie": "^0.0.5",
31+
"@tinyhttp/cookie-signature": "workspace:^0.0.4",
3132
"@tinyhttp/etag": "^0.1.15",
3233
"content-type": "^1.0.4",
34+
"encodeurl": "^1.0.2",
35+
"mime": "^2.4.6",
3336
"proxy-addr": "^2.0.6",
3437
"range-parser": "^1.2.1",
3538
"regexparam": "^1.3.0"
3639
},
3740
"devDependencies": {
3841
"@types/content-type": "^1.1.3",
42+
"@types/encodeurl": "^1.0.0",
43+
"@types/mime": "^2.0.2",
3944
"@types/proxy-addr": "^2.0.0",
4045
"@types/range-parser": "^1.2.3"
4146
}

packages/app/src/app.ts

Lines changed: 74 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createServer } from 'http'
1+
import { createServer, STATUS_CODES } from 'http'
22
import rg from 'regexparam'
33
import {
44
Request,
@@ -10,16 +10,22 @@ import {
1010
getRangeFromHeader,
1111
checkIfXMLHttpRequest,
1212
getHostname
13-
} from './classes/request'
14-
import { Response, send, json, status } from './classes/response'
13+
} from './request'
14+
import { Response, send, json, status, setCookie, clearCookie, setHeader } from './response'
1515
import { notFound } from './notFound'
1616

1717
export const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'HEAD']
1818

19-
export type Handler = (req: Request, res: Response) => void | Promise<void>
19+
export type Handler = (req: Request, res: Response, next?: () => void) => void | Promise<void>
2020

2121
export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'HEAD' | string
2222

23+
export const onError = (err: any, _req: Request, res: Response, _next: () => void) => {
24+
let code = (res.statusCode = err.code || err.status || 500)
25+
if (typeof err === 'string' || Buffer.isBuffer(err)) res.end(err)
26+
else res.end(err.message || STATUS_CODES[code])
27+
}
28+
2329
export interface Middleware {
2430
method?: Method
2531
handler: Handler
@@ -46,9 +52,11 @@ const createHandler = ({
4652
export class App {
4753
routes: Middleware[]
4854
middleware: Middleware[]
49-
constructor() {
55+
noMatchHandler: Handler
56+
constructor(noMatchHandler: Handler = notFound()) {
5057
this.routes = []
5158
this.middleware = []
59+
this.noMatchHandler = noMatchHandler
5260
}
5361

5462
get(url: string | Handler, handler?: Handler) {
@@ -83,68 +91,86 @@ export class App {
8391
})
8492
return this
8593
}
94+
handle(req: Request, res: Response) {
95+
/// Define extensions
8696

87-
listen(
88-
port?: number,
89-
cb = () => console.log(`Started on http://${host}:${port}`),
90-
host: string = 'localhost',
91-
backlog?: number
92-
) {
93-
// @ts-ignore
94-
const server = createServer((req: Request, res: Response) => {
95-
/// Define extensions
97+
/*
98+
Request extensions
99+
*/
100+
101+
req.app = this
102+
103+
const proto = getProtocol(req)
104+
const secure = proto === 'https'
105+
106+
req.protocol = proto
107+
req.secure = secure
108+
req.connection = Object.assign(req.socket, {
109+
encrypted: secure
110+
})
111+
112+
req.query = getQueryParams(req.url)
96113

97-
/*
98-
Request extensions
99-
*/
100-
const proto = getProtocol(req)
101-
const secure = proto === 'https'
102-
req.protocol = proto
103-
req.secure = secure
104-
req.connection = Object.assign(req.socket, {
105-
encrypted: secure
106-
})
114+
req.get = getHeader(req)
115+
req.range = getRangeFromHeader(req)
107116

108-
req.query = getQueryParams(req.url)
117+
req.xhr = checkIfXMLHttpRequest(req)
109118

110-
req.get = getHeader(req)
111-
req.range = getRangeFromHeader(req)
119+
req.hostname = getHostname(req)
112120

113-
req.xhr = checkIfXMLHttpRequest(req)
121+
/*
122+
Response extensions
123+
*/
124+
res.app = this
125+
res.header = res.set = setHeader(req, res)
126+
res.send = send(req, res)
127+
res.json = json(req, res)
128+
res.status = status(req, res)
114129

115-
req.hostname = getHostname(req)
130+
res.cookie = setCookie(req, res)
131+
res.clearCookie = clearCookie(req, res)
116132

117-
/*
118-
Response extensions
119-
*/
120-
res.send = send(req, res)
121-
res.json = json(req, res)
122-
res.status = status(req, res)
133+
for (const route of this.routes) {
134+
const { url, method, handler } = route
123135

124-
this.routes.map(({ url, method, handler }) => {
136+
if (res.writableEnded) {
137+
continue
138+
} else {
125139
if (req.method === method) {
126140
if (url && req.url && rg(url).pattern.test(req.url)) {
127141
req.params = getURLParams(req.url, url)
128142
req.route = getRouteFromApp(this, handler)
129-
if (!res.writableEnded) {
130-
res.statusCode = 200
131-
handler(req, res)
132-
}
143+
144+
res.statusCode = 200
145+
146+
handler(req, res)
133147
}
134148
}
135-
})
149+
}
150+
}
136151

137-
let middleware: Middleware[] = this.middleware.filter(m => m.handler.name !== 'logger')
152+
let middleware: Middleware[] = this.middleware.filter(m => m.handler.name !== 'logger')
138153

139-
middleware.push({ handler: notFound() })
154+
middleware.push({ handler: this.noMatchHandler })
140155

141-
const logger = this.middleware.find(m => m.handler.name === 'logger')
156+
const logger = this.middleware.find(m => m.handler.name === 'logger')
142157

143-
if (logger) middleware.push(logger)
158+
if (logger) middleware.push(logger)
144159

145-
middleware.map(({ handler }) => {
146-
handler(req, res)
147-
})
160+
middleware.map(({ handler }) => {
161+
handler(req, res)
162+
})
163+
}
164+
165+
listen(
166+
port?: number,
167+
cb = () => console.log(`Started on http://${host}:${port}`),
168+
host: string = 'localhost',
169+
backlog?: number
170+
) {
171+
// @ts-ignore
172+
const server = createServer((req: Request, res: Response) => {
173+
this.handle(req, res)
148174
})
149175

150176
return server.listen(port, host, backlog, cb)

0 commit comments

Comments
 (0)