Honoð¥
JWT Authentication Helper
JWTã¯ããŠã§ãã¢ããªã±ãŒã·ã§ã³ã§èªèšŒãšèªå¯ã®ããã«åºã䜿çšãããŠããããŒã¯ã³ã§ãããã®ãã«ããŒã¯ãJWTã®ãšã³ã³ãŒãããã³ãŒãã眲åãæ€èšŒã®ããã®æ©èœãæäŸããŸãã
ãŸãããã®ãã«ããŒã䜿ãã«ã¯ã次ã®ããã«ã€ã³ããŒãããŸãã
import { decode, sign, verify } from 'hono/jwt'
sign()
é¢æ°
ãã®é¢æ°ã¯ãæå®ãããã¢ã«ãŽãªãºã ãšã·ãŒã¯ã¬ããã䜿çšããŠãã€ããŒãã眲åããJWTããŒã¯ã³ãçæããŸãã
const payload = {
sub: 'user123',
role: 'admin',
}
const secret = 'mySecretKey'
const token = await sign(payload, secret)
-
payload
ã¯ã眲åãããJWTã®ãã€ããŒãã§ãã -
secret
ã¯ãJWTæ€èšŒã眲åã«äœ¿çšãããç§å¯éµã§ãã -
alg
ã¯ãJWT眲åãæ€èšŒã«äœ¿çšãããã¢ã«ãŽãªãºã ã§ãããã©ã«ãã¯HS256ã§ãã
verify()
é¢æ°
ãã®é¢æ°ã¯ãJWTããŒã¯ã³ãæ¬ç©ã§æå¹ã§ãããã©ããã確èªããŸããããŒã¯ã³ãå€æŽãããŠããªãããšã確èªãããã€ããŒãæ€èšŒãè¿œå ããå Žåã«ã®ã¿æå¹æ§ããã§ãã¯ããŸãã
const tokenToVerify = 'token'
const secretKey = 'mySecretKey'
const decodedPayload = await verify(tokenToVerify, secretKey)
console.log(decodedPayload)
-
token
ã¯ãæ€èšŒãããJWTããŒã¯ã³ã§ãã -
secret
ã¯ãJWTæ€èšŒã眲åã«äœ¿çšãããç§å¯éµã§ãã -
alg
ã¯ãJWT眲åãæ€èšŒã«äœ¿çšãããã¢ã«ãŽãªãºã ã§ãããã©ã«ãã¯HS256ã§ãã
decode()
é¢æ°
ãã®é¢æ°ã¯ã眲åæ€èšŒãè¡ããã«JWTããŒã¯ã³ããã³ãŒãããŸããããŒã¯ã³ããããããŒãšãã€ããŒããæœåºããŠè¿ããŸãã
const tokenToDecode = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAidXNlcjEyMyIsICJyb2xlIjogImFkbWluIn0.JxUwx6Ua1B0D1B0FtCrj72ok5cm1Pkmr_hL82sd7ELA'
const { header, payload } = decode(tokenToDecode)
console.log('Decoded Header:', header)
console.log('Decoded Payload:', payload)
-
token
ã¯ããã³ãŒããããJWTããŒã¯ã³ã§ãã
decode
é¢æ°ã䜿çšãããšãæ€èšŒãè¡ããã«JWTããŒã¯ã³ã®ããããŒãšãã€ããŒãã確èªã§ããŸããããã¯ããããã°ãJWTããŒã¯ã³ããæ
å ±ãæœåºããã®ã«äŸ¿å©ã§ãã
ãã€ããŒãæ€èšŒ
JWTããŒã¯ã³ãæ€èšŒããéã次ã®ãã€ããŒãæ€èšŒãè¡ãããŸãã
-
exp
ïŒããŒã¯ã³ã®æå¹æéãåããŠããªãããšã確èªããŸãã -
nbf
ïŒããŒã¯ã³ãæå®ãããæéããåã«äœ¿çšãããŠããªãããšã確èªããŸãã -
iat
ïŒããŒã¯ã³ãæªæ¥ã«çºè¡ãããŠããªãããšã確èªããŸãã
æ€èšŒæã«ãããã®ãã§ãã¯ãè¡ãå Žåã¯ãJWTãã€ããŒãã«ãããã®ãã£ãŒã«ãããªããžã§ã¯ããšããŠå«ããŠãã ããã
ã«ã¹ã¿ã ãšã©ãŒã¿ã€ã
ãã®ã¢ãžã¥ãŒã«ã§ã¯ãJWTé¢é£ã®ãšã©ãŒãåŠçããããã®ã«ã¹ã¿ã ãšã©ãŒã¿ã€ããå®çŸ©ãããŠããŸãã
-
JwtAlgorithmNotImplemented
ïŒèŠæ±ãããJWTã¢ã«ãŽãªãºã ãå®è£ ãããŠããªãããšã瀺ããŸãã -
JwtTokenInvalid
ïŒJWTããŒã¯ã³ãç¡å¹ã§ããããšã瀺ããŸãã -
JwtTokenNotBefore
ïŒããŒã¯ã³ãæå¹ãªæ¥ä»ããåã«äœ¿çšãããŠããããšã瀺ããŸãã -
JwtTokenExpired
ïŒããŒã¯ã³ã®æå¹æéãåããŠããããšã瀺ããŸãã -
JwtTokenIssuedAt
ïŒããŒã¯ã³ã®ãiatãã¯ã¬ãŒã ãæ£ãããªãããšã瀺ããŸãã -
JwtTokenSignatureMismatched
ïŒããŒã¯ã³ã®çœ²åãäžèŽããªãããšã瀺ããŸãã
ãµããŒããããŠããã¢ã«ãŽãªãºã ã¿ã€ã
ãã®ã¢ãžã¥ãŒã«ã¯ã次ã®JWTæå·åã¢ã«ãŽãªãºã ããµããŒãããŠããŸãã
-
HS256
ïŒSHA-256ã䜿çšããHMAC -
HS384
ïŒSHA-384ã䜿çšããHMAC -
HS512
ïŒSHA-512ã䜿çšããHMAC
JWTã¯ããŠã§ãã¢ããªã±ãŒã·ã§ã³ã§ãŠãŒã¶ãŒèªèšŒãšèªå¯ã管çããããã®åŒ·åãªæ¹æ³ã§ãããã®ãã«ããŒã䜿çšãããšãJWTã®çæãæ€èšŒããã³ãŒããç°¡åã«è¡ãããšãã§ããŸãã
HonoRequest
HonoRequest
ã¯ãHonoãã¬ãŒã ã¯ãŒã¯ã«ãããŠãHTTPãªã¯ãšã¹ãã®æ
å ±ãæ±ããªããžã§ã¯ãã§ããc.req
ãéããŠã¢ã¯ã»ã¹ã§ããŸãã
param()
ã¡ãœãã
param()
ã¡ãœããã¯ãURLã®ãã¹ãã©ã¡ãŒã¿ã®å€ãååŸããããã«äœ¿çšããŸãã
// ãã¹ãã©ã¡ãŒã¿ã®ååŸ
app.get('/entry/:id', (c) => {
const id = c.req.param('id')
// ...
})
// è€æ°ã®ãã¹ãã©ã¡ãŒã¿ãäžåºŠã«ååŸ
app.get('/entry/:id/comment/:commentId', (c) => {
const { id, commentId } = c.req.param()
// ...
})
query()
ã¡ãœãã
query()
ã¡ãœããã¯ãã¯ãšãªæååã®ãã©ã¡ãŒã¿ã®å€ãååŸããããã«äœ¿çšããŸãã
// ã¯ãšãªãã©ã¡ãŒã¿ã®ååŸ
app.get('/search', (c) => {
const query = c.req.query('q')
// ...
})
// è€æ°ã®ã¯ãšãªãã©ã¡ãŒã¿ãäžåºŠã«ååŸ
app.get('/search', (c) => {
const { q, limit, offset } = c.req.query()
// ...
})
queries()
ã¡ãœãã
queries()
ã¡ãœããã¯ãåãååã®è€æ°ã®ã¯ãšãªæååãã©ã¡ãŒã¿ã®å€ãååŸããããã«äœ¿çšããŸãã
app.get('/search', (c) => {
// tagsã¯æååã®é
åã«ãªããŸã
const tags = c.req.queries('tags')
// ...
})
header()
ã¡ãœãã
header()
ã¡ãœããã¯ããªã¯ãšã¹ããããã®å€ãååŸããããã«äœ¿çšããŸãã
app.get('/', (c) => {
const userAgent = c.req.header('User-Agent')
// ...
})
parseBody()
ã¡ãœãã
parseBody()
ã¡ãœããã¯ãmultipart/form-data
ãŸãã¯application/x-www-form-urlencoded
圢åŒã®ãªã¯ãšã¹ãããã£ã解æããããã«äœ¿çšããŸãã
app.post('/entry', async (c) => {
const body = await c.req.parseBody()
// ...
})
parseBody()
ã¡ãœããã¯ã以äžã®ãããªåäœããµããŒãããŠããŸãã
- åäžãã¡ã€ã«ã®ã¢ããããŒã
- è€æ°ãã¡ã€ã«ã®ã¢ããããŒã
- åãååã®è€æ°ãã¡ã€ã«ã®ã¢ããããŒã
json()
ã¡ãœãã
json()
ã¡ãœããã¯ãapplication/json
圢åŒã®ãªã¯ãšã¹ãããã£ã解æããããã«äœ¿çšããŸãã
app.post('/entry', async (c) => {
const body = await c.req.json()
// ...
})
text()
ã¡ãœãã
text()
ã¡ãœããã¯ãtext/plain
圢åŒã®ãªã¯ãšã¹ãããã£ã解æããããã«äœ¿çšããŸãã
app.post('/entry', async (c) => {
const body = await c.req.text()
// ...
})
arrayBuffer()
ã¡ãœãã
arrayBuffer()
ã¡ãœããã¯ããªã¯ãšã¹ãããã£ãArrayBuffer
ãšããŠè§£æããããã«äœ¿çšããŸãã
app.post('/entry', async (c) => {
const body = await c.req.arrayBuffer()
// ...
})
valid()
ã¡ãœãã
valid()
ã¡ãœããã¯ãããªããŒã·ã§ã³ãé©çšãããããŒã¿ãååŸããããã«äœ¿çšããŸãã
app.post('/posts', (c) => {
const { title, body } = c.req.valid('form')
// ...
})
䜿çšå¯èœãªã¿ãŒã²ããã¯ãform
ãjson
ãquery
ãheader
ãcookie
ãparam
ãªã©ããããŸãã
routePath()
ã¡ãœãã
routePath()
ã¡ãœããã¯ãç»é²ããããã¹ãååŸããããã«äœ¿çšããŸãã
app.get('/posts/:id', (c) => {
return c.json({ path: c.req.routePath })
})
matchedRoutes()
ã¡ãœãã
matchedRoutes()
ã¡ãœããã¯ããããããã«ãŒããååŸããããã«äœ¿çšããŸãããããã°ã«åœ¹ç«ã¡ãŸãã
app.use(async function logger(c, next) {
await next()
c.req.matchedRoutes.forEach(({ handler, method, path }, i) => {
const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]')
console.log(
method,
' ',
path,
' '.repeat(Math.max(10 - path.length, 0)),
name,
i === c.req.routeIndex ? '<- respond from here' : ''
)
})
})
path
ããããã£
path
ããããã£ã¯ããªã¯ãšã¹ãã®ãã¹åãååŸããããã«äœ¿çšããŸãã
app.get('/about/me', (c) => {
const pathname = c.req.path // `/about/me`
// ...
})
url
ããããã£
url
ããããã£ã¯ããªã¯ãšã¹ãã®URLãååŸããããã«äœ¿çšããŸãã
app.get('/about/me', (c) => {
const url = c.req.url // `http://localhost:8787/about/me`
// ...
})
method
ããããã£
method
ããããã£ã¯ããªã¯ãšã¹ãã®HTTPã¡ãœããåãååŸããããã«äœ¿çšããŸãã
app.get('/about/me', (c) => {
const method = c.req.method // `GET`
// ...
})
raw
ããããã£
raw
ããããã£ã¯ãçã®Request
ãªããžã§ã¯ããååŸããããã«äœ¿çšããŸãã
// Cloudflare Workersã®å Žå
app.post('/', async (c) => {
const metadata = c.req.raw.cf?.hostMetadata?
// ...
})
以äžããHonoRequest
ãªããžã§ã¯ãã®äž»èŠãªã¡ãœãããšããããã£ã®èª¬æã§ãããããã䜿çšããããšã§ãHTTPãªã¯ãšã¹ãã®æ
å ±ãç°¡åã«ååŸããåŠçããããšãã§ããŸãã
Cookie Helper
Cookie Helper ã¯ãéçºè ãã¯ãããŒãç°¡åã«ç®¡çã§ããããã«ããããã®ã€ã³ã¿ãŒãã§ãŒã¹ãæäŸããŸããã¯ãããŒã®èšå®ã解æãåé€ãªã©ã®æäœãç°¡åã«è¡ãããšãã§ããŸãã
ãŸããCookie Helper ã䜿ãã«ã¯ã次ã®ããã«ã€ã³ããŒãããŸãã
import { Hono } from 'hono'
import { getCookie, getSignedCookie, setCookie, setSignedCookie, deleteCookie } from 'hono/cookie'
䜿ãæ¹
ã¯ãããŒã®èšå®ãšååŸã«ã¯ã以äžã®ããã«ããŸãã
const app = new Hono()
app.get('/cookie', (c) => {
// ãã¹ãŠã®ã¯ãããŒãååŸ
const allCookies = getCookie(c)
// ç¹å®ã®ã¯ãããŒãååŸ
const yummyCookie = getCookie(c, 'yummy_cookie')
// ...
// ã¯ãããŒãèšå®
setCookie(c, 'delicious_cookie', 'macha')
// ã¯ãããŒãåé€
deleteCookie(c, 'delicious_cookie')
//
})
app.get('/signed-cookie', async (c) => {
const secret = 'secret ingredient'
// 眲åä»ãã¯ãããŒãååŸïŒçœ²åãæ¹ãããããŠãããç¡å¹ãªå Žå㯠`false` ãè¿ãïŒ
const allSignedCookies = await getSignedCookie(c, secret)
const fortuneCookie = await getSignedCookie(c, secret, 'fortune_cookie')
// ...
const anotherSecret = 'secret chocolate chips'
// 眲åä»ãã¯ãããŒãèšå®
await setSignedCookie(c, 'great_cookie', 'blueberry', anotherSecret)
// 眲åä»ãã¯ãããŒãåé€
deleteCookie(c, 'great_cookie')
//
})
眲åä»ãã¯ãããŒã®èšå®ãšååŸã¯éåæåŠçã«ãªããŸããããã¯ã眲åã®äœæã« WebCrypto API ã䜿çšããŠããããã§ãã
ãªãã·ã§ã³
setCookie
ãš setSignedCookie
ã«ã¯ä»¥äžã®ãªãã·ã§ã³ããããŸãã
-
domain
: string - ã¯ãããŒã®æå¹ãªãã¡ã€ã³ -
expires
: Date - ã¯ãããŒã®æå¹æé -
httpOnly
: boolean - ã¯ãããŒã HTTP ã®ã¿ã§å©çšå¯èœã«ãããã©ãã -
maxAge
: number - ã¯ãããŒã®æå¹æéïŒç§åäœïŒ -
path
: string - ã¯ãããŒã®æå¹ãªãã¹ -
secure
: boolean - ã¯ãããŒãã»ãã¥ã¢ãªæ¥ç¶ã§ã®ã¿å©çšå¯èœã«ãããã©ãã -
sameSite
:'Strict'
|'Lax'
|'None'
- ã¯ãããŒã® SameSite å±æ§ -
prefix
:secure
|'host'
- ã¯ãããŒåã®ãã¬ãã£ãã¯ã¹ -
partitioned
: boolean - ã¯ãããŒãããŒãã£ã·ã§ã³åãããã©ãã
äŸ:
// éåžžã®ã¯ãããŒ
setCookie(c, 'great_cookie', 'banana', {
path: '/',
secure: true,
domain: 'example.com',
httpOnly: true,
maxAge: 1000,
expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),
sameSite: 'Strict',
})
// 眲åä»ãã¯ãããŒ
await setSignedCookie(c, 'fortune_cookie', 'lots-of-money', 'secret ingredient', {
path: '/',
secure: true,
domain: 'example.com',
httpOnly: true,
maxAge: 1000,
expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),
sameSite: 'Strict',
})
deleteCookie
ã«ã¯ä»¥äžã®ãªãã·ã§ã³ããããŸãã
-
path
: string - åé€ããã¯ãããŒã®ãã¹ -
secure
: boolean - åé€ããã¯ãããŒãã»ãã¥ã¢ãã©ãã -
domain
: string - åé€ããã¯ãããŒã®ãã¡ã€ã³
äŸ:
deleteCookie(c, 'banana', {
path: '/',
secure: true,
domain: 'example.com',
})
__Secure-
ãš __Host-
ãã¬ãã£ãã¯ã¹
Cookie Helper ã¯ãã¯ãããŒåã® __Secure-
ãš __Host-
ãã¬ãã£ãã¯ã¹ããµããŒãããŠããŸãã
ã¯ãããŒåã«ãã¬ãã£ãã¯ã¹ããããã©ããã確èªããã«ã¯ããã¬ãã£ãã¯ã¹ãªãã·ã§ã³ãæå®ããŸãã
const securePrefixCookie = getCookie(c, 'yummy_cookie', 'secure')
const hostPrefixCookie = getCookie(c, 'yummy_cookie', 'host')
const securePrefixSignedCookie = await getSignedCookie(c, secret, 'fortune_cookie', 'secure')
const hostPrefixSignedCookie = await getSignedCookie(c, secret, 'fortune_cookie', 'host')
ãŸããã¯ãããŒãèšå®ããéã«ãã¬ãã£ãã¯ã¹ãæå®ããå Žåã¯ããã¬ãã£ãã¯ã¹ãªãã·ã§ã³ã«å€ãæå®ããŸãã
setCookie(c, 'delicious_cookie', 'macha', {
prefix: 'secure', // ãŸã㯠`host`
})
await setSignedCookie(c, 'delicious_cookie', 'macha', 'secret choco chips', {
prefix: 'secure', // ãŸã㯠`host`
})
ãã¹ããã©ã¯ãã£ã¹ã«åŸã
æ°ãã Cookie RFCïŒå¥å cookie-bisïŒãš CHIPS ã«ã¯ãéçºè ãåŸãã¹ãã¯ãããŒèšå®ã«é¢ããããã€ãã®ãã¹ããã©ã¯ãã£ã¹ãå«ãŸããŠããŸãã
- RFC6265bis-13
-
Max-Age
/Expires
ã®å¶é -
--Host-
/--Secure_
ãã¬ãã£ãã¯ã¹ã®å¶é
-
- CHIPS-01
-
Partitioned
ã®å¶é
-
Hono ã¯ãããã®ãã¹ããã©ã¯ãã£ã¹ã«åŸã£ãŠããŸããCookie Helper ã¯ã以äžã®æ¡ä»¶ã§ã¯ãããŒã解æãããšãã« Error
ãã¹ããŒããŸãã
- ã¯ãããŒåã
__Secure-
ã§å§ãŸã£ãŠããããsecure
ãªãã·ã§ã³ãèšå®ãããŠããªãå Žå - ã¯ãããŒåã
__Host-
ã§å§ãŸã£ãŠããããsecure
ãªãã·ã§ã³ãèšå®ãããŠããªãå Žå - ã¯ãããŒåã
__Host-
ã§å§ãŸã£ãŠããããpath
ã/
ã§ãªãå Žå - ã¯ãããŒåã
__Host-
ã§å§ãŸã£ãŠããããdomain
ãèšå®ãããŠããå Žå -
maxAge
ãªãã·ã§ã³ã®å€ã 400 æ¥ãã倧ããå Žå -
expires
ãªãã·ã§ã³ã®å€ãçŸåšæå»ãã 400 æ¥ããåŸã®å Žå
以äžããHono ã® Cookie Helper ã®è©³çŽ°ãªèª¬æã§ãããã® Helper ã䜿çšããããšã§ãã¯ãããŒã®èšå®ãååŸãåé€ãªã©ã®æäœãç°¡åãã€å®å šã«è¡ãããšãã§ããŸãããŸãããã¹ããã©ã¯ãã£ã¹ã«åŸã£ãŠã¯ãããŒã管çããããšã§ãã»ãã¥ãªãã£ãåäžãããããšãã§ããŸãã
CORS
CORSïŒCross-Origin Resource SharingïŒã¯ãWebã¢ããªã±ãŒã·ã§ã³ãHTTPãªã¯ãšã¹ããç°ãªããã¡ã€ã³ã«éä¿¡ããéã«äœ¿çšãããã»ãã¥ãªãã£ã¡ã«ããºã ã§ããCloudflare WorkersãWebAPIãšããŠäœ¿çšããå€éšã®ããã³ããšã³ãã¢ããªã±ãŒã·ã§ã³ããåŒã³åºãå ŽåãCORSãå®è£ ããå¿ èŠããããŸãã
Honoã® CORS ããã«ãŠã§ã¢ã䜿çšãããšãCORSã®èšå®ãç°¡åã«è¡ãããšãã§ããŸãã
ãŸããCORS ããã«ãŠã§ã¢ã䜿ãã«ã¯ã次ã®ããã«ã€ã³ããŒãããŸãã
import { Hono } from 'hono'
import { cors } from 'hono/cors'
次ã«ãHonoã¢ããªã±ãŒã·ã§ã³ã§ããã«ãŠã§ã¢ã䜿çšããŸãã
const app = new Hono()
app.use('/api/*', cors())
app.use(
'/api2/*',
cors({
origin: 'http://example.com',
allowHeaders: ['X-Custom-Header', 'Upgrade-Insecure-Requests'],
allowMethods: ['POST', 'GET', 'OPTIONS'],
exposeHeaders: ['Content-Length', 'X-Kuma-Revision'],
maxAge: 600,
credentials: true,
})
)
app.all('/api/abc', (c) => {
return c.json({ success: true })
})
app.all('/api2/abc', (c) => {
return c.json({ success: true })
})
äžèšã®äŸã§ã¯ã/api/*
ãã¹ã«å¯ŸããŠããã©ã«ãã®CORSèšå®ãé©çšããã/api2/*
ãã¹ã«å¯ŸããŠã«ã¹ã¿ã ã®CORSèšå®ãé©çšãããŠããŸãã
è€æ°ã®ãªãªãžã³ãèš±å¯ããå Žåã¯ã次ã®ããã«ããŸãã
app.use(
'/api3/*',
cors({
origin: ['https://example.com', 'https://example.org'],
})
)
// ãŸãã¯ãé¢æ°ã䜿çšããããšãã§ããŸã
app.use(
'/api4/*',
cors({
origin: (origin) => {
return origin.endsWith('.example.com') ? origin : 'http://example.com'
},
})
)
CORS ããã«ãŠã§ã¢ã®ãªãã·ã§ã³ã¯ä»¥äžã®éãã§ãã
-
origin
: string | string[] | (origin: string) => string- "Access-Control-Allow-Origin" CORSããããŒã®å€ãæå®ããŸããããã©ã«ãã¯
*
ã§ãã - ã³ãŒã«ããã¯é¢æ°ã䜿çšããŠããªãªãžã³ã«åºã¥ããŠåçã«å€ã決å®ããããšãã§ããŸãã
- "Access-Control-Allow-Origin" CORSããããŒã®å€ãæå®ããŸããããã©ã«ãã¯
-
allowMethods
: string[]- "Access-Control-Allow-Methods" CORSããããŒã®å€ãæå®ããŸããããã©ã«ãã¯
['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']
ã§ãã
- "Access-Control-Allow-Methods" CORSããããŒã®å€ãæå®ããŸããããã©ã«ãã¯
-
allowHeaders
: string[]- "Access-Control-Allow-Headers" CORSããããŒã®å€ãæå®ããŸããããã©ã«ãã¯
[]
ã§ãã
- "Access-Control-Allow-Headers" CORSããããŒã®å€ãæå®ããŸããããã©ã«ãã¯
-
maxAge
: number- "Access-Control-Max-Age" CORSããããŒã®å€ãæå®ããŸãã
-
credentials
: boolean- "Access-Control-Allow-Credentials" CORSããããŒã®å€ãæå®ããŸãã
-
exposeHeaders
: string[]- "Access-Control-Expose-Headers" CORSããããŒã®å€ãæå®ããŸããããã©ã«ãã¯
[]
ã§ãã
- "Access-Control-Expose-Headers" CORSããããŒã®å€ãæå®ããŸããããã©ã«ãã¯
ãããã®ãªãã·ã§ã³ãé©åã«èšå®ããããšã§ãCORSã®åäœãã«ã¹ã¿ãã€ãºã§ããŸãã
CORS ããã«ãŠã§ã¢ã䜿çšããããšã§ãCloudflare WorkersãWebAPIãšããŠäœ¿çšããéã®CORSèšå®ãç°¡åã«è¡ãããšãã§ããŸããé©åãªCORSèšå®ãè¡ãããšã§ãã»ãã¥ãªãã£ã確ä¿ãã€ã€ãå€éšã®ããã³ããšã³ãã¢ããªã±ãŒã·ã§ã³ããã®ãªã¯ãšã¹ããèš±å¯ããããšãã§ããŸãã
Routing
ã«ãŒãã£ã³ã°ãšã¯ãã¢ããªã±ãŒã·ã§ã³ãåãåã£ãHTTPãªã¯ãšã¹ãããé©åãªãã³ãã©é¢æ°ã«æ¯ãåããä»çµã¿ã®ããšã§ããHonoã®ã«ãŒãã£ã³ã°ã¯æè»ã§çŽæçã«èšå®ã§ããŸãã
åºæ¬çãªäœ¿ãæ¹
ãŸããåºæ¬çãªã«ãŒãã£ã³ã°ã®èšå®æ¹æ³ãèŠãŠãããŸãããã
// HTTP Methods
app.get('/', (c) => c.text('GET /'))
app.post('/', (c) => c.text('POST /'))
app.put('/', (c) => c.text('PUT /'))
app.delete('/', (c) => c.text('DELETE /'))
ãããã®ã¡ãœããã䜿çšãããšãç¹å®ã®HTTPã¡ãœããïŒGETãPOSTãPUTãDELETEïŒãšãã¹ã«å¯Ÿããã«ãŒããå®çŸ©ã§ããŸãã
ã¯ã€ã«ãã«ãŒãã䜿çšããã«ãŒãã£ã³ã°
ã¯ã€ã«ãã«ãŒãïŒ*
ïŒã䜿çšããŠããã¹ã®äžéšãä»»æã®æååã«ããããããããšãã§ããŸãã
app.get('/wild/*/card', (c) => {
return c.text('GET /wild/*/card')
})
ãã®äŸã§ã¯ã/wild/
ãš/card
ã®éã«ä»»æã®æååãå
¥ã£ããã¹ã«ãããããŸãã
ä»»æã®HTTPã¡ãœããã«ãããããã
all
ã¡ãœããã䜿çšãããšãä»»æã®HTTPã¡ãœããã«ããããããããšãã§ããŸãã
app.all('/hello', (c) => c.text('Any Method /hello'))
ã«ã¹ã¿ã HTTPã¡ãœããã®å®çŸ©
on
ã¡ãœããã䜿çšãããšãã«ã¹ã¿ã HTTPã¡ãœãããå®çŸ©ã§ããŸãã
app.on('PURGE', '/cache', (c) => c.text('PURGE Method /cache'))
è€æ°ã®HTTPã¡ãœããã«ãããããã
on
ã¡ãœããã«é
åãæž¡ãããšã§ãè€æ°ã®HTTPã¡ãœããã«ããããããããšãã§ããŸãã
app.on(['PUT', 'DELETE'], '/post', (c) => c.text('PUT or DELETE /post'))
è€æ°ã®ãã¹ã«ãããããã
on
ã¡ãœããã®ç¬¬2åŒæ°ã«é
åãæž¡ãããšã§ãè€æ°ã®ãã¹ã«ããããããããšãã§ããŸãã
app.on('GET', ['/hello', '/ja/hello', '/en/hello'], (c) => c.text('Hello'))
ãã¹ãã©ã¡ãŒã¿
ãã¹ãã©ã¡ãŒã¿ã䜿çšãããšããã¹ã®äžéšãå€æ°ãšããŠæ±ãããšãã§ããŸãã
app.get('/user/:name', (c) => {
const name = c.req.param('name')
// ...
})
ãã®äŸã§ã¯ã/user/
ã®åŸã«ä»»æã®æååãç¶ããã¹ã«ããããããã®æååãname
ãã©ã¡ãŒã¿ãšããŠååŸããŠããŸãã
ãªãã·ã§ã³ãã©ã¡ãŒã¿
ãã¹ãã©ã¡ãŒã¿ã®æ«å°Ÿã«?
ãä»ããããšã§ããªãã·ã§ã³ãã©ã¡ãŒã¿ãå®çŸ©ã§ããŸãã
// Will match `/api/animal` and `/api/animal/:type`
app.get('/api/animal/:type?', (c) => c.text('Animal!'))
ãã®äŸã§ã¯ã/api/animal
ãš/api/animal/:type
ã®äž¡æ¹ã®ãã¹ã«ãããããŸãã
æ£èŠè¡šçŸã«ããã«ãŒãã£ã³ã°
ãã¹ãã©ã¡ãŒã¿ã«æ£èŠè¡šçŸãæå®ããããšã§ããã现ããã«ãŒãã£ã³ã°ãå¯èœã§ãã
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
const { date, title } = c.req.param()
// ...
})
ãã®äŸã§ã¯ãdate
ãã©ã¡ãŒã¿ã¯æ°åã®ã¿ãtitle
ãã©ã¡ãŒã¿ã¯å°æåã®ã¿ã«ãããããŸãã
ã¹ã©ãã·ã¥ãå«ããã¹ãã©ã¡ãŒã¿
æ£èŠè¡šçŸã䜿çšããããšã§ãã¹ã©ãã·ã¥ãå«ããã¹ãã©ã¡ãŒã¿ãå®çŸ©ã§ããŸãã
app.get('/posts/:filename{.+\\.png$}', (c) => {
// ...
})
ãã®äŸã§ã¯ã.png
ã§çµãããã¡ã€ã«åã«ãããããŸãã
ãã§ãŒã³ã«ããã«ãŒãã£ã³ã°
åããã¹ã«å¯ŸããŠè€æ°ã®HTTPã¡ãœãããå®çŸ©ããå Žåããã§ãŒã³ã䜿çšãããšç°¡æœã«æžããŸãã
app
.get('/endpoint', (c) => {
return c.text('GET /endpoint')
})
.post((c) => {
return c.text('POST /endpoint')
})
.delete((c) => {
return c.text('DELETE /endpoint')
})
ã°ã«ãŒãå
Hono
ã€ã³ã¹ã¿ã³ã¹ã䜿çšããŠã«ãŒããã°ã«ãŒãåããroute
ã¡ãœããã䜿çšããŠã¡ã€ã³ã®ã¢ããªã±ãŒã·ã§ã³ã«è¿œå ã§ããŸãã
const book = new Hono()
book.get('/', (c) => c.text('List Books')) // GET /book
book.get('/:id', (c) => {
// GET /book/:id
const id = c.req.param('id')
return c.text('Get Book: ' + id)
})
book.post('/', (c) => c.text('Create Book')) // POST /book
const app = new Hono()
app.route('/book', book)
ããŒã¹ãã¹
basePath
ã¡ãœããã䜿çšããŠãã°ã«ãŒãã®ããŒã¹ãã¹ãæå®ã§ããŸãã
const api = new Hono().basePath('/api')
api.get('/book', (c) => c.text('List Books')) // GET /api/book
ãã¹ãåãå«ãã«ãŒãã£ã³ã°
Hono
ã³ã³ã¹ãã©ã¯ã¿ã®getPath
ãªãã·ã§ã³ãèšå®ããããšã§ããã¹ãåãå«ãã«ãŒãã£ã³ã°ãå¯èœã§ãã
const app = new Hono({
getPath: (req) => req.url.replace(/^https?:\/(.+?)$/, '$1'),
})
app.get('/www1.example.com/hello', (c) => c.text('hello www1'))
app.get('/www2.example.com/hello', (c) => c.text('hello www2'))
ãã¹ãããããŒã®å€ã«ããã«ãŒãã£ã³ã°
Hono
ã³ã³ã¹ãã©ã¯ã¿ã®getPath
ãªãã·ã§ã³ãèšå®ããããšã§ããã¹ãããããŒã®å€ã«ããã«ãŒãã£ã³ã°ãå¯èœã§ãã
const app = new Hono({
getPath: (req) =>
'/' + req.headers.get('host') + req.url.replace(/^https?:\/\/[^/]+(\/[^?]*)/, '$1'),
})
app.get('/www1.example.com/hello', () => c.text('hello www1'))
ãã®äŸã§ã¯ãhost
ããããŒã®å€ãwww1.example.com
ã®å Žåã«ãããããŸãã
ã«ãŒãã£ã³ã°ã®åªå
é äœ
ãã³ãã©ãããã«ãŠã§ã¢ã¯ãç»é²ãããé çªã«å®è¡ãããŸãã
app.get('/book/a', (c) => c.text('a')) // a
app.get('/book/:slug', (c) => c.text('common')) // common
ãã®äŸã§ã¯ã/book/a
ãžã®ãªã¯ãšã¹ãã¯a
ãã³ãã©ã«ããããã/book/b
ãžã®ãªã¯ãšã¹ãã¯common
ãã³ãã©ã«ãããããŸãã
ãã³ãã©ãå®è¡ããããšãåŠçã¯åæ¢ããŸãã
app.get('*', (c) => c.text('common')) // common
app.get('/foo', (c) => c.text('foo')) // foo
ãã®äŸã§ã¯ã/foo
ãžã®ãªã¯ãšã¹ãã¯common
ãã³ãã©ã«ãããããfoo
ãã³ãã©ã¯å®è¡ãããŸããã
å¿ ãå®è¡ãããããã«ãŠã§ã¢ãããå Žåã¯ããã³ãã©ããåã«ç»é²ããŸãã
app.use(logger())
app.get('/foo', (c) => c.text('foo'))
ãã©ãŒã«ããã¯ãã³ãã©ãèšå®ãããå Žåã¯ãä»ã®ãã³ãã©ããåŸã«ç»é²ããŸãã
app.get('/foo', (c) => c.text('foo')) // foo
app.get('*', (c) => c.text('fallback')) // fallback
ãã®äŸã§ã¯ã/bar
ãžã®ãªã¯ãšã¹ãã¯fallback
ãã³ãã©ã«ãããããŸãã
ã°ã«ãŒãåã®æ³šæç¹
ã°ã«ãŒãåã®éã¯ãã«ãŒãã£ã³ã°ã®é çªã«æ³šæãå¿
èŠã§ããroute
é¢æ°ã¯ã第2åŒæ°ä»¥éã®ã«ãŒãã£ã³ã°ããèªèº«ã®ã«ãŒãã£ã³ã°ã«è¿œå ããŸãã
three.get('/hi', (c) => c.text('hi'))
two.route('/three', three)
app.route('/two', two)
export default app
ãã®äŸã§ã¯ã/two/three/hi
ãžã®ãªã¯ãšã¹ãã¯hi
ãã³ãã©ã«ãããããŸãã
ãã ããé çªãééã£ãŠãããšã404ãšã©ãŒã«ãªããŸãã
three.get('/hi', (c) => c.text('hi'))
app.route('/two', two) // `two` does not have routes
two.route('/three', three)
export default app
ãã®äŸã§ã¯ã/two/three/hi
ãžã®ãªã¯ãšã¹ãã¯404ãšã©ãŒã«ãªããŸãã
以äžããHonoã®ã«ãŒãã£ã³ã°ã«ã€ããŠã®è©³çŽ°ãªèª¬æã§ããHonoã®ã«ãŒãã£ã³ã°ã¯æè»ã§åŒ·åãªæ©èœãæäŸããŠãããã¢ããªã±ãŒã·ã§ã³ã®ããŒãºã«åãããŠé©åã«èšå®ããããšãã§ããŸãã
CSRF Protection
CSRFïŒCross-Site Request ForgeryïŒã¯ããŠã§ãã¢ããªã±ãŒã·ã§ã³ã®è匱æ§ãå©çšããæ»æææ³ã®äžã€ã§ããæ»æè ã¯ããŠãŒã¶ãŒãæå³ããªããªã¯ãšã¹ãããŠãŒã¶ãŒã®æš©éã§å®è¡ãããããšãã§ããŸãã
Hono ã® CSRF ä¿è·ããã«ãŠã§ã¢ã¯ããã®ãã㪠CSRF æ»æãé²ãããã«äœ¿çšãããŸãããã®ããã«ãŠã§ã¢ã¯ããªã¯ãšã¹ãããããŒããã§ãã¯ããããšã§ãCSRF æ»æãé²ããŸãã
å
·äœçã«ã¯ããã©ãŒã èŠçŽ ã䜿çšããéä¿¡ãªã©ã® CSRF æ»æãé²ãããã«ãOrigin
ããããŒã®å€ãšãªã¯ãšã¹ãããã URL ãæ¯èŒããŸãã
ãã ããOrigin
ããããŒãéä¿¡ããªãå€ããã©ãŠã¶ãããªããŒã¹ãããã·ã䜿çšã㊠Origin
ããããŒãåé€ããç°å¢ã§ã¯ãããŸãåäœããªãå¯èœæ§ããããŸãããã®ãããªç°å¢ã§ã¯ãä»ã® CSRF ããŒã¯ã³ã®æ¹æ³ã䜿çšããå¿
èŠããããŸãã
ãŸããCSRF ä¿è·ããã«ãŠã§ã¢ã䜿ãã«ã¯ã次ã®ããã«ã€ã³ããŒãããŸãã
import { Hono } from 'hono'
import { csrf } from 'hono/csrf'
次ã«ãHono ã¢ããªã±ãŒã·ã§ã³ã§ããã«ãŠã§ã¢ã䜿çšããŸãã
const app = new Hono()
app.use(csrf())
ããã«ãããããã©ã«ãã®èšå®ã§ CSRF ä¿è·ãæå¹ã«ãªããŸãã
ãªãã·ã§ã³ã§ãèš±å¯ãããªãªãžã³ãæå®ããããšãã§ããŸãã
æååã§æå®ããå ŽåïŒ
app.use(csrf({ origin: 'myapp.example.com' }))
æååã®é åã§æå®ããå ŽåïŒ
app.use(
csrf({
origin: ['myapp.example.com', 'development.myapp.example.com'],
})
)
é¢æ°ã§æå®ããå ŽåïŒ
app.use(
'*',
csrf({
origin: (origin) => /https:\/\/(\w+\.)?myapp\.example\.com$/.test(origin),
})
)
é¢æ°ã§æå®ããå Žåã¯ããããã³ã«ãæ€èšŒã㊠$
ã«äžèŽããããšã匷ããå§ãããŸããåæ¹äžèŽã䜿çšããŠã¯ãããŸããã
CSRF ä¿è·ããã«ãŠã§ã¢ã®ãªãã·ã§ã³ã¯ä»¥äžã®éãã§ãã
-
origin
: string | string[] | Function- èš±å¯ãããªãªãžã³ãæå®ããŸãã
CSRF ä¿è·ã¯ããŠã§ãã¢ããªã±ãŒã·ã§ã³ã®ã»ãã¥ãªãã£ã確ä¿ããããã«éèŠãªæ©èœã§ããHono ã® CSRF ä¿è·ããã«ãŠã§ã¢ã䜿çšããããšã§ãCSRF æ»æãé²ããã¢ããªã±ãŒã·ã§ã³ã®ã»ãã¥ãªãã£ãåäžãããããšãã§ããŸãã
ãã ããCSRF ä¿è·ããã«ãŠã§ã¢ã«ãå¶éãããããšã«æ³šæããŠãã ãããOrigin
ããããŒãéä¿¡ããªãå€ããã©ãŠã¶ãããªããŒã¹ãããã·ã䜿çšããç°å¢ã§ã¯ãããŸãåäœããªãå¯èœæ§ããããŸãããã®ãããªç°å¢ã§ã¯ãä»ã® CSRF 察çãæ€èšããå¿
èŠããããŸãã
CSRF ä¿è·ã¯ããŠã§ãã¢ããªã±ãŒã·ã§ã³ã®ã»ãã¥ãªãã£ã確ä¿ããããã®éèŠãªèŠçŽ ã®äžã€ã§ããHono ã® CSRF ä¿è·ããã«ãŠã§ã¢ãé©åã«äœ¿çšããããšã§ãã¢ããªã±ãŒã·ã§ã³ã®ã»ãã¥ãªãã£ãåäžãããããšãã§ããŸãã
HonoRequest
HonoRequest
ã¯ãHonoãã¬ãŒã ã¯ãŒã¯ã«ãããŠãHTTPãªã¯ãšã¹ãã®æ
å ±ãæ±ããªããžã§ã¯ãã§ããc.req
ãéããŠã¢ã¯ã»ã¹ã§ããŸãã
param()
ã¡ãœãã
param()
ã¡ãœããã¯ãURLã®ãã¹ãã©ã¡ãŒã¿ã®å€ãååŸããããã«äœ¿çšããŸãã
// ãã¹ãã©ã¡ãŒã¿ã®ååŸ
app.get('/entry/:id', (c) => {
const id = c.req.param('id')
// ...
})
// è€æ°ã®ãã¹ãã©ã¡ãŒã¿ãäžåºŠã«ååŸ
app.get('/entry/:id/comment/:commentId', (c) => {
const { id, commentId } = c.req.param()
// ...
})
query()
ã¡ãœãã
query()
ã¡ãœããã¯ãã¯ãšãªæååã®ãã©ã¡ãŒã¿ã®å€ãååŸããããã«äœ¿çšããŸãã
// ã¯ãšãªãã©ã¡ãŒã¿ã®ååŸ
app.get('/search', (c) => {
const query = c.req.query('q')
// ...
})
// è€æ°ã®ã¯ãšãªãã©ã¡ãŒã¿ãäžåºŠã«ååŸ
app.get('/search', (c) => {
const { q, limit, offset } = c.req.query()
// ...
})
queries()
ã¡ãœãã
queries()
ã¡ãœããã¯ãåãååã®è€æ°ã®ã¯ãšãªæååãã©ã¡ãŒã¿ã®å€ãååŸããããã«äœ¿çšããŸãã
app.get('/search', (c) => {
// tagsã¯æååã®é
åã«ãªããŸã
const tags = c.req.queries('tags')
// ...
})
header()
ã¡ãœãã
header()
ã¡ãœããã¯ããªã¯ãšã¹ããããã®å€ãååŸããããã«äœ¿çšããŸãã
app.get('/', (c) => {
const userAgent = c.req.header('User-Agent')
// ...
})
parseBody()
ã¡ãœãã
parseBody()
ã¡ãœããã¯ãmultipart/form-data
ãŸãã¯application/x-www-form-urlencoded
圢åŒã®ãªã¯ãšã¹ãããã£ã解æããããã«äœ¿çšããŸãã
app.post('/entry', async (c) => {
const body = await c.req.parseBody()
// ...
})
parseBody()
ã¡ãœããã¯ã以äžã®ãããªåäœããµããŒãããŠããŸãã
- åäžãã¡ã€ã«ã®ã¢ããããŒã
- è€æ°ãã¡ã€ã«ã®ã¢ããããŒã
- åãååã®è€æ°ãã¡ã€ã«ã®ã¢ããããŒã
json()
ã¡ãœãã
json()
ã¡ãœããã¯ãapplication/json
圢åŒã®ãªã¯ãšã¹ãããã£ã解æããããã«äœ¿çšããŸãã
app.post('/entry', async (c) => {
const body = await c.req.json()
// ...
})
text()
ã¡ãœãã
text()
ã¡ãœããã¯ãtext/plain
圢åŒã®ãªã¯ãšã¹ãããã£ã解æããããã«äœ¿çšããŸãã
app.post('/entry', async (c) => {
const body = await c.req.text()
// ...
})
arrayBuffer()
ã¡ãœãã
arrayBuffer()
ã¡ãœããã¯ããªã¯ãšã¹ãããã£ãArrayBuffer
ãšããŠè§£æããããã«äœ¿çšããŸãã
app.post('/entry', async (c) => {
const body = await c.req.arrayBuffer()
// ...
})
valid()
ã¡ãœãã
valid()
ã¡ãœããã¯ãããªããŒã·ã§ã³ãé©çšãããããŒã¿ãååŸããããã«äœ¿çšããŸãã
app.post('/posts', (c) => {
const { title, body } = c.req.valid('form')
// ...
})
䜿çšå¯èœãªã¿ãŒã²ããã¯ãform
ãjson
ãquery
ãheader
ãcookie
ãparam
ãªã©ããããŸãã
routePath()
ã¡ãœãã
routePath()
ã¡ãœããã¯ãç»é²ããããã¹ãååŸããããã«äœ¿çšããŸãã
app.get('/posts/:id', (c) => {
return c.json({ path: c.req.routePath })
})
matchedRoutes()
ã¡ãœãã
matchedRoutes()
ã¡ãœããã¯ããããããã«ãŒããååŸããããã«äœ¿çšããŸãããããã°ã«åœ¹ç«ã¡ãŸãã
app.use(async function logger(c, next) {
await next()
c.req.matchedRoutes.forEach(({ handler, method, path }, i) => {
const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]')
console.log(
method,
' ',
path,
' '.repeat(Math.max(10 - path.length, 0)),
name,
i === c.req.routeIndex ? '<- respond from here' : ''
)
})
})
path
ããããã£
path
ããããã£ã¯ããªã¯ãšã¹ãã®ãã¹åãååŸããããã«äœ¿çšããŸãã
app.get('/about/me', (c) => {
const pathname = c.req.path // `/about/me`
// ...
})
url
ããããã£
url
ããããã£ã¯ããªã¯ãšã¹ãã®URLãååŸããããã«äœ¿çšããŸãã
app.get('/about/me', (c) => {
const url = c.req.url // `http://localhost:8787/about/me`
// ...
})
method
ããããã£
method
ããããã£ã¯ããªã¯ãšã¹ãã®HTTPã¡ãœããåãååŸããããã«äœ¿çšããŸãã
app.get('/about/me', (c) => {
const method = c.req.method // `GET`
// ...
})
raw
ããããã£
raw
ããããã£ã¯ãçã®Request
ãªããžã§ã¯ããååŸããããã«äœ¿çšããŸãã
// Cloudflare Workersã®å Žå
app.post('/', async (c) => {
const metadata = c.req.raw.cf?.hostMetadata?
// ...
})
以äžããHonoRequest
ãªããžã§ã¯ãã®äž»èŠãªã¡ãœãããšããããã£ã®èª¬æã§ãããããã䜿çšããããšã§ãHTTPãªã¯ãšã¹ãã®æ
å ±ãç°¡åã«ååŸããåŠçããããšãã§ããŸãã
JSX Renderer ããã«ãŠã§ã¢
JSX Renderer ããã«ãŠã§ã¢ã䜿çšãããšãc.setRenderer()
ã䜿çšããã«ãc.render()
é¢æ°ã§ JSX ãã¬ã³ããªã³ã°ããéã®ã¬ã€ã¢ãŠããèšå®ã§ããŸããããã«ãuseRequestContext()
ã䜿çšããŠãã³ã³ããŒãã³ãå
㧠Context ã®ã€ã³ã¹ã¿ã³ã¹ã«ã¢ã¯ã»ã¹ã§ããŸãã
ã€ã³ããŒãâ
npmDeno
ts
import { Hono } from 'hono'
import { jsxRenderer, useRequestContext } from 'hono/jsx-renderer'
䜿çšæ¹æ³â
jsx
const app = new Hono()
app.get(
'/page/*',
jsxRenderer(({ children }) => {
return (
<html>
<body>
<header>ã¡ãã¥ãŒ</header>
<div>{children}</div>
</body>
</html>
)
})
)
app.get('/page/about', (c) => {
return c.render(<h1>ç§ã«ã€ããŠ</h1>)
})
ãªãã·ã§ã³â
docType
: boolean
| string
â
HTML ã®å
é ã« DOCTYPE ãè¿œå ããããªãå Žåã¯ãdocType
ãªãã·ã§ã³ã false
ã«èšå®ããŸãã
tsx
app.use(
'*',
jsxRenderer(
({ children }) => {
return (
<html>
<body>{children}</body>
</html>
)
},
{ docType: false }
)
)
ãŸããDOCTYPE ãæå®ããããšãã§ããŸãã
tsx
app.use(
'*',
jsxRenderer(
({ children }) => {
return (
<html>
<body>{children}</body>
</html>
)
},
{
docType:
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
}
)
)
stream
: boolean
| Record<string, string>
â
true
ã«èšå®ããããRecord å€ãæäŸãããšãã¹ããªãŒãã³ã°ã¬ã¹ãã³ã¹ãšããŠã¬ã³ããªã³ã°ãããŸãã
tsx
const AsyncComponent = async () => {
await new Promise((r) => setTimeout(r, 1000)) // 1ç§ã¹ãªãŒã
return <div>ããã«ã¡ã¯ïŒ</div>
}
app.get(
'*',
jsxRenderer(
({ children }) => {
return (
<html>
<body>
<h1>SSR ã¹ããªãŒãã³ã°</h1>
{children}
</body>
</html>
)
},
{ stream: true }
)
)
app.get('/', (c) => {
return c.render(
<Suspense fallback={<div>èªã¿èŸŒã¿äž...</div>}>
<AsyncComponent />
</Suspense>
)
})
true
ãèšå®ãããŠããå Žåã次ã®ããããŒãè¿œå ãããŸãã
ts
{
'Transfer-Encoding': 'chunked',
'Content-Type': 'text/html; charset=UTF-8'
}
Record å€ãæå®ããããšã§ãããããŒã®å€ãã«ã¹ã¿ãã€ãºã§ããŸãã
ãã¹ããããã¬ã€ã¢ãŠãâ
Layout
ã³ã³ããŒãã³ãã䜿çšãããšãã¬ã€ã¢ãŠãããã¹ãã§ããŸãã
tsx
app.use(
jsxRenderer(({ children }) => {
return (
<html>
<body>{children}</body>
</html>
)
})
)
const blog = new Hono()
blog.use(
jsxRenderer(({ children, Layout }) => {
return (
<Layout>
<nav>ããã°ã¡ãã¥ãŒ</nav>
<div>{children}</div>
</Layout>
)
})
)
app.route('/blog', blog)
useRequestContext()
â
useRequestContext()
㯠Context ã®ã€ã³ã¹ã¿ã³ã¹ãè¿ããŸãã
tsx
const RequestUrlBadge: FC = () => {
const c = useRequestContext()
return <b>{c.req.url}</b>
}
app.get('/page/info', (c) => {
return c.render(
<div>
ã¢ã¯ã»ã¹äž: <RequestUrlBadge />
</div>
)
})
ContextRenderer
ã®æ¡åŒµâ
以äžã®ããã« ContextRenderer
ãå®çŸ©ããããšã§ãã¬ã³ãã©ãŒã«è¿œå ã®ã³ã³ãã³ããæž¡ãããšãã§ããŸããããã¯ãäŸãã°ãããŒãžã«å¿ã㊠head ã¿ã°ã®å
容ãå€æŽãããå Žåã«äŸ¿å©ã§ãã
tsx
declare module 'hono' {
interface ContextRenderer {
(content: string | Promise<string>, props: { title: string }): Response
}
}
const app = new Hono()
app.get(
'/page/*',
jsxRenderer(({ children, title }) => {
return (
<html>
<head>
<title>{title}</title>
</head>
<body>
<header>ã¡ãã¥ãŒ</header>
<div>{children}</div>
</body>
</html>
)
})
)
app.get('/page/favorites', (c) => {
return c.render(
<div>
<ul>
<li>寿åžãé£ã¹ãããš</li>
<li>éç芳æŠ</li>
</ul>
</div>,
{
title: 'ç§ã®ãæ°ã«å
¥ã',
}
)
})
ãã®ããŒãžã GitHub ã§ç·šé