mirror of
https://github.com/honojs/hono.git
synced 2024-11-24 19:26:56 +01:00
feat: Add new shortcuts for request/response (#62)
* Add new shortcuts for request/response * We have only `param`, we can not use `params` * Update readme * tweak
This commit is contained in:
parent
ff375c6b69
commit
68444b6932
94
README.md
94
README.md
@ -35,7 +35,7 @@ Fastest is hono
|
||||
|
||||
Below is a demonstration to create an application of Cloudflare Workers with Hono.
|
||||
|
||||
![Demo](https://user-images.githubusercontent.com/10682/148223268-2484a891-57c1-472f-9df3-936a5586f002.gif)
|
||||
![Demo](https://user-images.githubusercontent.com/10682/151102477-be0f950e-8d23-49c5-b6d8-d8ecb6b7484e.gif)
|
||||
|
||||
## Install
|
||||
|
||||
@ -90,7 +90,7 @@ app.all('/hello', (c) => c.text('Any Method /hello'))
|
||||
|
||||
```js
|
||||
app.get('/user/:name', (c) => {
|
||||
const name = c.req.params('name')
|
||||
const name = c.req.param('name')
|
||||
...
|
||||
})
|
||||
```
|
||||
@ -99,8 +99,8 @@ app.get('/user/:name', (c) => {
|
||||
|
||||
```js
|
||||
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
|
||||
const date = c.req.params('date')
|
||||
const title = c.req.params('title')
|
||||
const date = c.req.param('date')
|
||||
const title = c.req.param('title')
|
||||
...
|
||||
```
|
||||
|
||||
@ -219,13 +219,18 @@ To handle Request and Reponse easily, you can use Context object:
|
||||
### c.req
|
||||
|
||||
```js
|
||||
|
||||
// Get Request object
|
||||
app.get('/hello', (c) => {
|
||||
const userAgent = c.req.headers.get('User-Agent')
|
||||
...
|
||||
})
|
||||
|
||||
// Shortcut to get a header value
|
||||
app.get('/shortcut', (c) => {
|
||||
const userAgent = c.req.header('User-Agent')
|
||||
...
|
||||
})
|
||||
|
||||
// Query params
|
||||
app.get('/search', (c) => {
|
||||
const query = c.req.query('q')
|
||||
@ -234,40 +239,31 @@ app.get('/search', (c) => {
|
||||
|
||||
// Captured params
|
||||
app.get('/entry/:id', (c) => {
|
||||
const id = c.req.params('id')
|
||||
const id = c.req.param('id')
|
||||
...
|
||||
})
|
||||
```
|
||||
|
||||
### c.res
|
||||
### Shortcuts for Response
|
||||
|
||||
```js
|
||||
// Response object
|
||||
app.use('/', (c, next) => {
|
||||
next()
|
||||
c.res.headers.append('X-Debug', 'Debug message')
|
||||
})
|
||||
```
|
||||
|
||||
### c.event
|
||||
|
||||
```js
|
||||
// FetchEvent object
|
||||
app.use('*', async (c, next) => {
|
||||
c.event.waitUntil(
|
||||
...
|
||||
)
|
||||
await next()
|
||||
})
|
||||
```
|
||||
|
||||
### c.env
|
||||
|
||||
```js
|
||||
// Environment object for Cloudflare Workers
|
||||
app.get('*', async c => {
|
||||
const counter = c.env.COUNTER
|
||||
...
|
||||
app.get('/welcome', (c) => {
|
||||
c.header('X-Message', 'Hello!')
|
||||
c.header('Content-Type', 'text/plain')
|
||||
c.status(201)
|
||||
c.statusText('201 Content Created')
|
||||
return c.body('Thank you for comming')
|
||||
/*
|
||||
Same as:
|
||||
return new Response('Thank you for comming', {
|
||||
status: 201,
|
||||
statusText: '201 Content Created',
|
||||
headers: {
|
||||
'X-Message': 'Hello',
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
})
|
||||
*/
|
||||
})
|
||||
```
|
||||
|
||||
@ -310,6 +306,38 @@ app.get('/redirect', (c) => c.redirect('/'))
|
||||
app.get('/redirect-permanently', (c) => c.redirect('/', 301))
|
||||
```
|
||||
|
||||
### c.res
|
||||
|
||||
```js
|
||||
// Response object
|
||||
app.use('/', (c, next) => {
|
||||
next()
|
||||
c.res.headers.append('X-Debug', 'Debug message')
|
||||
})
|
||||
```
|
||||
|
||||
### c.event
|
||||
|
||||
```js
|
||||
// FetchEvent object
|
||||
app.use('*', async (c, next) => {
|
||||
c.event.waitUntil(
|
||||
...
|
||||
)
|
||||
await next()
|
||||
})
|
||||
```
|
||||
|
||||
### c.env
|
||||
|
||||
```js
|
||||
// Environment object for Cloudflare Workers
|
||||
app.get('*', async c => {
|
||||
const counter = c.env.COUNTER
|
||||
...
|
||||
})
|
||||
```
|
||||
|
||||
## fire
|
||||
|
||||
`app.fire()` do:
|
||||
|
@ -55,7 +55,7 @@ app.get('/hello', () => new Response('This is /hello'))
|
||||
|
||||
// Named parameter
|
||||
app.get('/entry/:id', (c) => {
|
||||
const id = c.req.params('id')
|
||||
const id = c.req.param('id')
|
||||
return c.text(`Your ID is ${id}`)
|
||||
})
|
||||
// Redirect
|
||||
@ -71,7 +71,7 @@ app.get('/fetch-url', async (c) => {
|
||||
|
||||
// Request headers
|
||||
app.get('/user-agent', (c) => {
|
||||
const userAgent = c.req.headers.get('User-Agent')
|
||||
const userAgent = c.req.header('User-Agent')
|
||||
return c.text(`Your UserAgent is ${userAgent}`)
|
||||
})
|
||||
|
||||
@ -90,8 +90,8 @@ app.post('/api/posts', (c) => c.json({ message: 'Created!' }, 201))
|
||||
// default route
|
||||
app.get('/api/*', (c) => c.text('API endpoint is not found', 404))
|
||||
|
||||
app.post('/form', async (ctx) => {
|
||||
return ctx.json(ctx.req.parsedBody || {})
|
||||
app.post('/form', async (c) => {
|
||||
return c.json(c.req.parsedBody || {})
|
||||
//return new Response('ok /form')
|
||||
})
|
||||
|
||||
|
@ -24,7 +24,7 @@ export const create: Handler = async (c) => {
|
||||
}
|
||||
|
||||
export const show: Handler = async (c) => {
|
||||
const id = c.req.params('id')
|
||||
const id = c.req.param('id')
|
||||
const post = await Model.getPost(id)
|
||||
if (!post) {
|
||||
return c.json({ error: 'Not Found', ok: false }, 404)
|
||||
@ -33,7 +33,7 @@ export const show: Handler = async (c) => {
|
||||
}
|
||||
|
||||
export const update: Handler = async (c) => {
|
||||
const id = c.req.params('id')
|
||||
const id = c.req.param('id')
|
||||
if (!(await Model.getPost(id))) {
|
||||
// 204 No Content
|
||||
return c.json({ ok: false }, 204)
|
||||
@ -44,7 +44,7 @@ export const update: Handler = async (c) => {
|
||||
}
|
||||
|
||||
export const destroy: Handler = async (c) => {
|
||||
const id = c.req.params('id')
|
||||
const id = c.req.param('id')
|
||||
if (!(await Model.getPost(id))) {
|
||||
// 204 No Content
|
||||
return c.json({ ok: false }, 204)
|
||||
|
@ -10,7 +10,7 @@ app.use('*', async (c, next) => {
|
||||
app.get('/', (c) => c.text('Hono!! Compute@Edge!!'))
|
||||
|
||||
app.get('/hello/:name', (c) => {
|
||||
return c.text(`Hello ${c.req.params('name')}!!!!!`)
|
||||
return c.text(`Hello ${c.req.param('name')}!!!!!`)
|
||||
})
|
||||
|
||||
app.fire()
|
||||
|
@ -48,7 +48,7 @@ app.get('/', (c) => {
|
||||
})
|
||||
|
||||
app.get('/post/:id{[0-9]+}', (c) => {
|
||||
const id = c.req.params('id')
|
||||
const id = c.req.param('id')
|
||||
const post = getPost(id)
|
||||
if (!post) {
|
||||
return c.text('Not Found', 404)
|
||||
|
@ -9,6 +9,10 @@ export class Context {
|
||||
res: Response
|
||||
env: Env
|
||||
event: FetchEvent
|
||||
private _headers: Headers
|
||||
private _status: number
|
||||
private _statusText: string
|
||||
body: (body: BodyInit, init?: ResponseInit) => Response
|
||||
|
||||
constructor(req: Request, opts?: { res: Response; env: Env; event: FetchEvent }) {
|
||||
this.req = req
|
||||
@ -17,9 +21,26 @@ export class Context {
|
||||
this.env = opts.env
|
||||
this.event = opts.event
|
||||
}
|
||||
this._headers = {}
|
||||
this.body = this.newResponse
|
||||
}
|
||||
|
||||
newResponse(body?: BodyInit | null | undefined, init?: ResponseInit | undefined): Response {
|
||||
header(name: string, value: string): void {
|
||||
this._headers[name] = value
|
||||
}
|
||||
|
||||
status(number: number): void {
|
||||
this._status = number
|
||||
}
|
||||
|
||||
statusText(text: string): void {
|
||||
this._statusText = text
|
||||
}
|
||||
|
||||
newResponse(body: BodyInit, init: ResponseInit = {}): Response {
|
||||
init.status = this._status || init.status
|
||||
init.statusText = this._statusText || init.statusText
|
||||
init.headers = { ...init.headers, ...this._headers }
|
||||
return new Response(body, init)
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,9 @@ const METHOD_NAME_OF_ALL = 'ALL'
|
||||
|
||||
declare global {
|
||||
interface Request {
|
||||
params: (key: string) => string
|
||||
param: (key: string) => string
|
||||
query: (key: string) => string | null
|
||||
header: (name: string) => string
|
||||
parsedBody: any
|
||||
}
|
||||
}
|
||||
@ -126,7 +127,7 @@ export class Hono {
|
||||
|
||||
const result = await this.matchRoute(method, path)
|
||||
|
||||
request.params = (key: string): string => {
|
||||
request.param = (key: string): string => {
|
||||
if (result) {
|
||||
return result.params[key]
|
||||
}
|
||||
|
@ -6,6 +6,9 @@ export const defaultMiddleware = async (c: Context, next: Function) => {
|
||||
const url = new URL(c.req.url)
|
||||
return url.searchParams.get(key)
|
||||
}
|
||||
c.req.header = (name: string): string => {
|
||||
return c.req.headers.get(name)
|
||||
}
|
||||
|
||||
await next()
|
||||
|
||||
|
@ -18,7 +18,7 @@ describe('Logger by Middleware', () => {
|
||||
app.get('/empty', () => new Response(''))
|
||||
|
||||
it('Log status 200 with empty body', async () => {
|
||||
const req = new Request('http://localhost/short')
|
||||
const req = new Request('http://localhost/empty')
|
||||
const res = await app.dispatch(req)
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
@ -44,7 +44,7 @@ describe('Logger by Middleware', () => {
|
||||
expect(log.endsWith(` ${longRandomString.length / 1024}kB`)).toBe(true)
|
||||
})
|
||||
|
||||
it.only('Log status 404', async () => {
|
||||
it('Log status 404', async () => {
|
||||
const msg = 'Default 404 Nout Found'
|
||||
app.notFound = () => {
|
||||
return new Response(msg, { status: 404 })
|
||||
|
@ -4,7 +4,7 @@ describe('Context', () => {
|
||||
const req = new Request('http://localhost/')
|
||||
const c = new Context(req)
|
||||
|
||||
it('c.text', async () => {
|
||||
it('c.text()', async () => {
|
||||
const res = c.text('text in c', 201, { 'X-Custom': 'Message' })
|
||||
expect(res.status).toBe(201)
|
||||
expect(res.headers.get('Content-Type')).toBe('text/plain')
|
||||
@ -12,7 +12,7 @@ describe('Context', () => {
|
||||
expect(res.headers.get('X-Custom')).toBe('Message')
|
||||
})
|
||||
|
||||
it('c.json', async () => {
|
||||
it('c.json()', async () => {
|
||||
const res = c.json({ message: 'Hello' }, 201, { 'X-Custom': 'Message' })
|
||||
expect(res.status).toBe(201)
|
||||
expect(res.headers.get('Content-Type')).toMatch('application/json')
|
||||
@ -22,7 +22,7 @@ describe('Context', () => {
|
||||
expect(res.headers.get('X-Custom')).toBe('Message')
|
||||
})
|
||||
|
||||
it('c.html', async () => {
|
||||
it('c.html()', async () => {
|
||||
const res = c.html('<h1>Hello! Hono!</h1>', 201, { 'X-Custom': 'Message' })
|
||||
expect(res.status).toBe(201)
|
||||
expect(res.headers.get('Content-Type')).toMatch('text/html')
|
||||
@ -30,7 +30,7 @@ describe('Context', () => {
|
||||
expect(res.headers.get('X-Custom')).toBe('Message')
|
||||
})
|
||||
|
||||
it('c.redirect', async () => {
|
||||
it('c.redirect()', async () => {
|
||||
let res = c.redirect('/destination')
|
||||
expect(res.status).toBe(302)
|
||||
expect(res.headers.get('Location')).toMatch(/^https?:\/\/.+\/destination$/)
|
||||
@ -38,4 +38,30 @@ describe('Context', () => {
|
||||
expect(res.status).toBe(302)
|
||||
expect(res.headers.get('Location')).toBe('https://example.com/destination')
|
||||
})
|
||||
|
||||
it('c.header()', async () => {
|
||||
c.header('X-Foo', 'Bar')
|
||||
const res = c.body('Hi')
|
||||
const foo = res.headers.get('X-Foo')
|
||||
expect(foo).toBe('Bar')
|
||||
})
|
||||
|
||||
it('c.status() and c.statusText()', async () => {
|
||||
c.status(201)
|
||||
c.statusText('Created!!!!')
|
||||
const res = c.body('Hi')
|
||||
expect(res.status).toBe(201)
|
||||
expect(res.statusText).toBe('Created!!!!')
|
||||
})
|
||||
|
||||
it('Complext pattern', async () => {
|
||||
c.status(404)
|
||||
c.statusText('Hono is Not Found')
|
||||
const res = c.json({ hono: 'great app' })
|
||||
expect(res.status).toBe(404)
|
||||
expect(res.statusText).toBe('Hono is Not Found')
|
||||
expect(res.headers.get('Content-Type')).toMatch(/json/)
|
||||
const obj: { [key: string]: string } = await res.json()
|
||||
expect(obj['hono']).toBe('great app')
|
||||
})
|
||||
})
|
||||
|
@ -2,16 +2,26 @@ import { Hono } from '../src/hono'
|
||||
|
||||
describe('GET Request', () => {
|
||||
const app = new Hono()
|
||||
|
||||
app.get('/hello', () => {
|
||||
return new Response('hello', {
|
||||
status: 200,
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/hello-with-shortcuts', (c) => {
|
||||
c.header('X-Custom', 'This is Hono')
|
||||
c.statusText('Hono is created')
|
||||
c.status(201)
|
||||
return c.html('<h1>Hono!!!</h1>')
|
||||
})
|
||||
|
||||
app.notFound = () => {
|
||||
return new Response('not found', {
|
||||
status: 404,
|
||||
})
|
||||
}
|
||||
|
||||
it('GET /hello is ok', async () => {
|
||||
const req = new Request('http://localhost/hello')
|
||||
const res = await app.dispatch(req)
|
||||
@ -19,6 +29,18 @@ describe('GET Request', () => {
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('hello')
|
||||
})
|
||||
|
||||
it('GET /hell-with-shortcuts is ok', async () => {
|
||||
const req = new Request('http://localhost/hello-with-shortcuts')
|
||||
const res = await app.dispatch(req)
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(201)
|
||||
expect(res.statusText).toBe('Hono is created')
|
||||
expect(res.headers.get('X-Custom')).toBe('This is Hono')
|
||||
expect(res.headers.get('Content-Type')).toMatch(/text\/html/)
|
||||
expect(await res.text()).toBe('<h1>Hono!!!</h1>')
|
||||
})
|
||||
|
||||
it('GET / is not found', async () => {
|
||||
const req = new Request('http://localhost/')
|
||||
const res = await app.dispatch(req)
|
||||
@ -84,35 +106,51 @@ describe('Routing', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('params and query', () => {
|
||||
describe('param and query', () => {
|
||||
const app = new Hono()
|
||||
|
||||
app.get('/entry/:id', async (c) => {
|
||||
const id = await c.req.params('id')
|
||||
app.get('/entry/:id', (c) => {
|
||||
const id = c.req.param('id')
|
||||
return new Response(`id is ${id}`, {
|
||||
status: 200,
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/search', async (c) => {
|
||||
const name = await c.req.query('name')
|
||||
app.get('/search', (c) => {
|
||||
const name = c.req.query('name')
|
||||
return new Response(`name is ${name}`, {
|
||||
status: 200,
|
||||
})
|
||||
})
|
||||
|
||||
it('params of /entry/:id is found', async () => {
|
||||
app.get('/add-header', (c) => {
|
||||
const bar = c.req.header('X-Foo')
|
||||
return new Response(`foo is ${bar}`, {
|
||||
status: 200,
|
||||
})
|
||||
})
|
||||
|
||||
it('param of /entry/:id is found', async () => {
|
||||
const req = new Request('http://localhost/entry/123')
|
||||
const res = await app.dispatch(req)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('id is 123')
|
||||
})
|
||||
|
||||
it('query of /search?name=sam is found', async () => {
|
||||
const req = new Request('http://localhost/search?name=sam')
|
||||
const res = await app.dispatch(req)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('name is sam')
|
||||
})
|
||||
|
||||
it('/add-header header - X-Foo is Bar', async () => {
|
||||
const req = new Request('http://localhost/add-header')
|
||||
req.headers.append('X-Foo', 'Bar')
|
||||
const res = await app.dispatch(req)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('foo is Bar')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Middleware', () => {
|
||||
@ -127,24 +165,24 @@ describe('Middleware', () => {
|
||||
// Apeend Custom Header
|
||||
app.use('*', async (c, next) => {
|
||||
await next()
|
||||
await c.res.headers.append('x-custom', 'root')
|
||||
c.res.headers.append('x-custom', 'root')
|
||||
})
|
||||
|
||||
app.use('/hello', async (c, next) => {
|
||||
await next()
|
||||
await c.res.headers.append('x-message', 'custom-header')
|
||||
c.res.headers.append('x-message', 'custom-header')
|
||||
})
|
||||
|
||||
app.use('/hello/*', async (c, next) => {
|
||||
await next()
|
||||
await c.res.headers.append('x-message-2', 'custom-header-2')
|
||||
c.res.headers.append('x-message-2', 'custom-header-2')
|
||||
})
|
||||
|
||||
app.get('/hello', () => {
|
||||
return new Response('hello')
|
||||
})
|
||||
app.get('/hello/:message', (c) => {
|
||||
const message = c.req.params('message')
|
||||
const message = c.req.param('message')
|
||||
return new Response(`${message}`)
|
||||
})
|
||||
|
||||
@ -153,18 +191,18 @@ describe('Middleware', () => {
|
||||
const res = await app.dispatch(req)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('hello')
|
||||
expect(await res.headers.get('x-custom')).toBe('root')
|
||||
expect(await res.headers.get('x-message')).toBe('custom-header')
|
||||
expect(await res.headers.get('x-message-2')).toBe('custom-header-2')
|
||||
expect(res.headers.get('x-custom')).toBe('root')
|
||||
expect(res.headers.get('x-message')).toBe('custom-header')
|
||||
expect(res.headers.get('x-message-2')).toBe('custom-header-2')
|
||||
})
|
||||
|
||||
it('logging and custom header with named params', async () => {
|
||||
it('logging and custom header with named param', async () => {
|
||||
const req = new Request('http://localhost/hello/message')
|
||||
const res = await app.dispatch(req)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('message')
|
||||
expect(await res.headers.get('x-custom')).toBe('root')
|
||||
expect(await res.headers.get('x-message-2')).toBe('custom-header-2')
|
||||
expect(res.headers.get('x-custom')).toBe('root')
|
||||
expect(res.headers.get('x-message-2')).toBe('custom-header-2')
|
||||
})
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user