Hono OpenAPI Valibot Prisma REST API๐ฅ
ใฏใใใซ
โHono Advent Calendar 2024ใทใชใผใบ๏ผใฎ๏ผ๏ผๆฅ็ฎใฎ่จไบใงใใ
โHono OpenAPI
ใงใValibot
ใไฝฟ็จใงใใใใใซใชใฃใใฎใงใ่ฉฆใใฆใฟใพใใใ
ๅ่ใชใณใฏ
ใใฃใฌใฏใใชๆง้
.
โโโ apps
โ โโโ hono-openapi-valibot
โ โโโ package.json
โ โโโ src
โ โ โโโ handler
โ โ โ โโโ comments_handler.ts
โ โ โ โโโ hono_handler.ts
โ โ โ โโโ posts_handler.ts
โ โ โโโ index.ts
โ โ โโโ service
โ โ โโโ comments_service.ts
โ โ โโโ posts_service.ts
โ โโโ tsconfig.json
โโโ biome.json
โโโ docs
โ โโโ ER.md
โโโ package.json
โโโ packages
โ โโโ prisma
โ โ โโโ index.ts
โ โ โโโ package.json
โ โ โโโ schema.prisma
โ โ โโโ tsconfig.json
โ โโโ schema
โ โโโ index.ts
โ โโโ package.json
โ โโโ tsconfig.json
โโโ pnpm-workspace.yaml
Prisma
โๆๅใซใschema.prisma
ใ็จๆใใพใใ
schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
/// @r Post.id Comment.postId one-to-many
model Post {
/// The unique identifier for the post.
/// @v.pipe(v.string(), v.uuid())
id String @id @default(uuid())
/// The title of the post.
/// @v.pipe(v.string(), v.minLength(1), v.maxLength(40))
title String
/// The content of the post.
/// @v.pipe(v.string(), v.minLength(1), v.maxLength(140))
content String
/// The list of comments associated with the post.
comments Comment[]
/// The date and time when the post was created.
/// @v.pipe(v.string(), v.regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/))
createdAt DateTime @default(now())
/// The date and time when the post was last updated.
/// Automatically updated to the current timestamp on each update.
/// @v.pipe(v.string(), v.regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/))
updatedAt DateTime @updatedAt
}
model Comment {
/// The unique identifier for the comment.
/// @v.pipe(v.string(), v.uuid())
id String @id @default(uuid())
/// The unique identifier of the post this comment belongs to.
/// @z.string().uuid()
/// @v.pipe(v.string(), v.uuid())
postId String
/// The post that this comment is associated with.
/// Establishes a relationship to the Post model.
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
/// The content of the comment.
/// @v.pipe(v.string(), v.minLength(1), v.maxLength(140))
content String
/// The date and time when the comment was created.
/// @v.pipe(v.string(), v.regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/))
createdAt DateTime @default(now())
}
โไปฅไธใฎใใใซ่จ่ฟฐใใใจใERๅณใValibot
ในใญใผใใZod
ในใญใผใใ็ๆใใใใใผใซใ@prisma/generator-helper
ใ็จใใฆใ็พๅจ้็บ้ไธญใงใใ๏ผๆใพใงใซใฏใใฉใคใใฉใชใจใใฆๆไพใงใใใใใซๆบๅใ้ฒใใฆใใพใใ
-
@r : ใชใฌใผใทใงใณ้ขไฟใ่จ่ฟฐ
-
@v : Valibotใฎใใชใใผใทใงใณใ่จ่ฟฐ
-
@z : Zodใฎใใชใใผใทใงใณใ่จ่ฟฐ
โใใใฑใผใธๅใจใขใคใณใณใฏใใพใ ๆฑบใใฆใใชใใฎใงใ่ฏใๆกใใใใฐๅ้ไธญใงใใ
ๅ่ใชใณใฏ
โPrisma
ใฎใธใงใใฌใผใฟใผใๅฎ่กใใพใใ
ER
Valibot
โv.date()
ใไฝฟ็จใใฆใใชใ็็ฑใฏใdescribeRoute
ใงใๅฏพๅฟใใใฆใใชใใใใงใใ
import * as v from 'valibot'
/**
* Post model
*
* Relationships:
*
* - Post.id - Comment.postId (one-to-many)
*/
export const postSchema = v.object({
/** The unique identifier for the post. */
id: v.pipe(v.string(), v.uuid()),
/** The title of the post. */
title: v.pipe(v.string(), v.minLength(1), v.maxLength(40)),
/** The content of the post. */
content: v.pipe(v.string(), v.minLength(1), v.maxLength(140)),
/** The date and time when the post was created. */
createdAt: v.pipe(v.string(), v.regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/)),
/** The date and time when the post was last updated. */
updatedAt: v.pipe(v.string(), v.regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/)),
})
export const commentSchema = v.object({
/** The unique identifier for the comment. */
id: v.pipe(v.string(), v.uuid()),
/** The unique identifier of the post this comment belongs to. */
postId: v.pipe(v.string(), v.uuid()),
/** The content of the comment. */
content: v.pipe(v.string(), v.minLength(1), v.maxLength(140)),
/** The date and time when the comment was created. */
createdAt: v.pipe(v.string(), v.regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/)),
})
export type Post = v.InferInput<typeof postSchema>
export type Comment = v.InferInput<typeof commentSchema>
export const postSchemaWithRelationsSchema = v.object({
postSchema,
commentToPost: v.array(commentSchema),
})
export const commentSchemaWithRelationsSchema = v.object({
commentSchema,
commentToPost: postSchema,
})
export type postSchemaWithRelations = v.InferInput<typeof postSchemaWithRelationsSchema>
export type commentSchemaWithRelations = v.InferInput<typeof commentSchemaWithRelationsSchema>
Hono OpenAPI
โapps/hono-openapi-valibot/src/index.ts
โscalar
ใจswagger
ใฉใกใใ่กจ็คบใใใพใใ
import { Hono } from 'hono'
import { serve } from '@hono/node-server'
import { openAPISpecs } from 'hono-openapi'
import { apiReference } from '@scalar/hono-api-reference'
import { SwaggerUI } from '@hono/swagger-ui'
import honoHandler from './handler/hono_handler.js'
import postsHandler from './handler/posts_handler.js'
import commentsHandler from './handler/comments_handler.js'
const app = new Hono()
const port = 3000
console.log(`Server is running on http://localhost:${port}`)
app.use('*', async (c, next) => {
try {
await next()
} catch (e) {
return c.json({ error: (e as Error).message }, 500)
}
})
app.get(
'/openapi',
openAPISpecs(app, {
documentation: {
info: {
title: 'Hono API',
version: '1.0.0',
description: 'Hono Valibot API',
},
tags: [
{
name: 'Hono',
description: 'Hono API',
},
{
name: 'Post',
description: 'Post API',
},
{
name: 'Comment',
description: 'Comment API',
},
],
servers: [{ url: 'http://localhost:3000', description: 'Local Server' }],
},
}),
)
// scalar
app.get(
'/docs',
apiReference({
theme: 'saturn',
spec: {
url: '/openapi',
},
}),
)
// swagger
app.get('/ui', (c) => {
return c.html(`
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Custom Swagger" />
<title>Custom Swagger</title>
<script>
// custom script
</script>
<style>
/* custom style */
</style>
</head>
${SwaggerUI({ url: '/openapi' })}
</html>
`)
})
serve({
fetch: app.fetch,
port,
})
const api = app
.route('/', honoHandler)
.route('/posts', postsHandler)
.route('/comments', commentsHandler)
export default api
REST APIใฎไฝๆ
apps/hono-openapi-valibot/src/handler/hono_handler.ts
import { Hono } from 'hono'
import { describeRoute } from 'hono-openapi'
import { resolver } from 'hono-openapi/valibot'
import * as v from 'valibot'
const responseSchema = v.string()
const honoHandler = new Hono().get(
'/',
describeRoute({
tags: ['Hono'],
description: 'Hono Valibot๐ฅ',
responses: {
200: {
description: 'Hono Valibot๐ฅ',
content: {
'application/json': {
schema: resolver(responseSchema),
},
},
},
},
}),
(c) => {
return c.json({ message: 'Hono Valibot๐ฅ' }, 200)
},
)
export default honoHandler
apps/hono-openapi-valibot/src/handler/posts_handler.ts
import { Hono } from 'hono'
import { describeRoute } from 'hono-openapi'
import { resolver, validator } from 'hono-openapi/valibot'
import { postSchema } from '@packages/schema'
import * as v from 'valibot'
import {
deletePostsId,
getPosts,
getPostsId,
postPosts,
putPostsId,
} from '../service/posts_service.js'
const errorSchema = v.object({ message: v.string() })
const postsHandler = new Hono()
.post(
'/',
describeRoute({
tags: ['Post'],
description: 'Create a post',
responses: {
201: {
description: 'Post created successfully',
content: {
'application/json': { schema: resolver(postSchema) },
},
},
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
},
}),
validator(
'json',
v.object({
title: v.pipe(v.string(), v.minLength(1), v.maxLength(40)),
content: v.pipe(v.string(), v.minLength(1), v.maxLength(140)),
}),
),
async (c) => {
const { title, content } = c.req.valid('json')
const post = await postPosts(title, content)
return c.json(post)
},
)
.get(
'/:id',
describeRoute({
tags: ['Post'],
description: 'Get a post by ID.',
responses: {
200: {
description: 'Post fetched successfully',
content: { 'application/json': { schema: resolver(postSchema) } },
},
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
},
}),
validator('param', v.object({ id: v.pipe(v.string(), v.uuid()) })),
async (c) => {
const { id } = c.req.valid('param')
const post = await getPostsId(id)
return c.json(post)
},
)
.get(
'/',
describeRoute({
tags: ['Post'],
description: 'Get posts',
responses: {
200: {
description: 'Posts fetched successfully',
content: {
'application/json': { schema: resolver(postSchema) },
},
},
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
},
}),
validator(
'query',
v.object({
page: v.string(),
rows: v.string(),
}),
),
async (c) => {
const { page, rows } = c.req.valid('query')
const pageNumber = parseInt(page)
const rowsPerPage = parseInt(rows)
if (isNaN(pageNumber) || isNaN(rowsPerPage) || pageNumber < 0 || rowsPerPage < 1) {
return c.json({ message: 'Bad Request' }, 400)
}
const limit = rowsPerPage
const offset = (pageNumber - 1) * rowsPerPage
const posts = await getPosts(limit, offset)
return c.json(posts)
},
)
.put(
'/:id',
describeRoute({
tags: ['Post'],
description: 'Update a post by ID.',
responses: {
204: {
description: 'Post successfully updated.',
},
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
},
}),
validator('param', v.object({ id: v.pipe(v.string(), v.uuid()) })),
validator(
'json',
v.object({
title: v.optional(v.pipe(v.string(), v.minLength(1), v.maxLength(40))),
content: v.optional(v.pipe(v.string(), v.minLength(1), v.maxLength(140))),
}),
),
async (c) => {
const { id } = c.req.valid('param')
const { title, content } = c.req.valid('json')
await putPostsId(id, title, content)
return new Response(null, { status: 204 })
},
)
.delete(
'/:id',
describeRoute({
tags: ['Post'],
description: 'Delete a post by ID.',
responses: {
204: {
description: 'Post successfully deleted.',
},
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
},
}),
validator('param', v.object({ id: v.pipe(v.string(), v.uuid()) })),
async (c) => {
const { id } = c.req.valid('param')
await deletePostsId(id)
return new Response(null, { status: 204 })
},
)
export default postsHandler
apps/hono-openapi-valibot/src/handler/comments_handler.ts
import { Hono } from 'hono'
import { describeRoute } from 'hono-openapi'
import {
deleteCommentsId,
getComments,
postComments,
putCommentsId,
} from '../service/comments_service.js'
import { resolver, validator } from 'hono-openapi/valibot'
import { postSchema, commentSchema } from '@packages/schema'
import * as v from 'valibot'
const errorSchema = v.object({ message: v.string() })
const commentsHandler = new Hono()
.post(
'/:id',
describeRoute({
tags: ['Comment'],
description: 'Create a comment',
responses: {
201: {
description: 'Comment created successfully',
content: { 'application/json': { schema: resolver(commentSchema) } },
},
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
},
}),
validator('param', v.pick(postSchema, ['id'])),
validator('json', v.pick(commentSchema, ['content'])),
async (c) => {
const { id } = c.req.valid('param')
const { content } = c.req.valid('json')
const comment = await postComments(id, content)
return c.json(comment)
},
)
.get(
'/:id',
describeRoute({
tags: ['Comment'],
description: 'Get a comment by ID.',
responses: {
200: {
description: 'Comment fetched successfully',
content: { 'application/json': { schema: resolver(commentSchema) } },
},
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
404: {
description: 'Comment not found.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
},
}),
validator('param', v.pick(postSchema, ['id'])),
async (c) => {
const { id } = c.req.valid('param')
const comment = await getComments(id)
if (comment.length === 0) {
return c.json({ message: 'Comment not found' }, 404)
}
return c.json(comment)
},
)
.put(
'/:id',
describeRoute({
tags: ['Comment'],
description: 'Update a comment by ID.',
responses: {
204: {
description: 'Comment successfully updated.',
},
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
},
}),
validator('param', v.pick(commentSchema, ['id'])),
validator('json', v.pick(commentSchema, ['content'])),
async (c) => {
const { id } = c.req.valid('param')
const { content } = c.req.valid('json')
await putCommentsId(id, content)
return new Response(null, { status: 204 })
},
)
.delete(
'/:id',
describeRoute({
tags: ['Comment'],
description: 'Delete a comment by ID.',
responses: {
204: {
description: 'Comment successfully deleted.',
},
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: resolver(errorSchema) } },
},
},
}),
validator('param', v.pick(commentSchema, ['id'])),
async (c) => {
const { id } = c.req.valid('param')
await deleteCommentsId(id)
return new Response(null, { status: 204 })
},
)
export default commentsHandler
โHono OpenAPI
ใงใฏใdescribeRoute
ใ็จใใใใจใงใOpenAPI
ใฎใใญใฅใกใณใใ็ๆใใใใจใใงใใพใใ
OpenAPI
Scalar
Swagger
REST ClientใงใAPIใฎใฌในใใณในใ็ขบ่ช
ใชใฏใจในใ
GET http://localhost:3000/
Content-Type: application/json
Accept: application/json
ใฌในใใณใน
HTTP/1.1 200 OK
content-type: application/json
Content-Length: 30
Date: ***, ** *** **** **:**:** GMT
Connection: keep-alive
keep-alive: timeout=5
{
"message": "Hono Valibot๐ฅ"
}
ๆ็จฟใชใฏใจในใ
POST http://localhost:3000/posts HTTP/1.1
Content-Type: application/json
Accept: application/json
{
"title": "Hono OpenAPI Valibot Prisma REST API๐ฅ",
"content": "Hono OpenAPI Valibot Prisma REST API๐ฅ ใ่ฉฆใใฆใฟใพใใใ"
}
ใฌในใใณใน
HTTP/1.1 200 OK
content-type: application/json
Content-Length: 255
Date: ***, ** *** **** **:**:** GMT
Connection: keep-alive
keep-alive: timeout=5
{
"id": "************************************",
"title": "Hono OpenAPI Valibot Prisma REST API๐ฅ",
"content": "Hono OpenAPI Valibot Prisma REST API๐ฅ ใ่ฉฆใใฆใฟใพใใใ",
"createdAt": "***, ** *** **** **:**:** GMT",
"updatedAt": "***, ** *** **** **:**:** GMT"
}
ใฏใจใชใใฉใกใผใฟใงใใชใฏใจในใ
GET http://localhost:3000/posts?page=1&rows=10 HTTP/1.1
Content-Type: application/json
Accept: application/json
ใฌในใใณใน
HTTP/1.1 200 OK
content-type: application/json
Content-Length: 2391
Date: ***, ** *** **** **:**:** GMT
Connection: keep-alive
keep-alive: timeout=5
[
{
"id": "************************************",
"title": "Prisma Drizzle",
"content": "Prisma Drizzle ใฎๆฏ่ผใ",
"createdAt": "***, ** *** **** **:**:** GMT",
"updatedAt": "***, ** *** **** **:**:** GMT",
"comments": []
},
{
"id": "************************************",
"title": "Hono๐ฅ Prisma",
"content": "Hono๐ฅ Prisma ใ่ฉฆใใฆใฟใพใใใ",
"createdAt": "***, ** *** **** **:**:** GMT",
"updatedAt": "***, ** *** **** **:**:** GMT",
"comments": []
},
{
"id": "************************************",
"title": "Hono๐ฅ Drizzle",
"content": "Hono๐ฅ Drizzle ใ่ฉฆใใฆใฟใพใใใ",
"createdAt": "***, ** *** **** **:**:** GMT",
"updatedAt": "***, ** *** **** **:**:** GMT",
"comments": []
},
{
"id": "************************************",
"title": "Hono๐ฅ ใจ Mojollicious",
"content": "Hono๐ฅ ใจ Mojollicious ใซไผผใฆใใพใใ",
"createdAt": "***, ** *** **** **:**:** GMT",
"updatedAt": "***, ** *** **** **:**:** GMT",
"comments": []
},
{
"id": "************************************",
"title": "Hono๐ฅ ใจ Express",
"content": "Hono๐ฅใฏใExpressใฎไปฃๆฟใจใชใใคใคใใใพใใ",
"createdAt": "***, ** *** **** **:**:** GMT",
"updatedAt": "***, ** *** **** **:**:** GMT",
"comments": []
},
{
"id": "************************************",
"title": "Hono Takibi๐ฅ",
"content": "Hono Takibi๐ฅใฏใOpenAPIใฎๅฎ็พฉใใใZod OpenAPI Honoใฎใณใผใใ็ๆใใใใผใซใงใใ",
"createdAt": "***, ** *** **** **:**:** GMT",
"updatedAt": "***, ** *** **** **:**:** GMT",
"comments": []
},
{
"id": "************************************",
"title": "Hono Validator๐ฅ",
"content": "Hono Validator๐ฅ ใ่ฉฆใใฆใฟใพใใใ",
"createdAt": "***, ** *** **** **:**:** GMT",
"updatedAt": "***, ** *** **** **:**:** GMT",
"comments": []
},
{
"id": "************************************",
"title": "HonoX๐ฅ ใ่ฉฆใใฆใฟใ",
"content": "HonoX๐ฅ ใ่ฉฆใใฆใฟใพใใใ",
"createdAt": "***, ** *** **** **:**:** GMT",
"updatedAt": "***, ** *** **** **:**:** GMT",
"comments": []
},
{
"id": "************************************",
"title": "Hono OpenAPI Zod Prisma REST API๐ฅ",
"content": "Hono OpenAPI Zod Prisma REST API๐ฅ ใ่ฉฆใใฆใฟใพใใใ",
"createdAt": "***, ** *** **** **:**:** GMT",
"updatedAt": "***, ** *** **** **:**:** GMT",
"comments": []
},
{
"id": "************************************",
"title": "Hono OpenAPI Valibot Prisma REST API๐ฅ",
"content": "Hono OpenAPI Valibot Prisma REST API๐ฅ ใ่ฉฆใใฆใฟใพใใใ",
"createdAt": "***, ** *** **** **:**:** GMT",
"updatedAt": "***, ** *** **** **:**:** GMT",
"comments": []
}
]
ใฏใจใชใใฉใกใผใฟใๆๅฎใใ๏ผใใผใธ็ฎใๅๅพ
GET http://localhost:3000/posts?page=2&rows=10 HTTP/1.1
Content-Type: application/json
Accept: application/json
```sh
HTTP/1.1 200 OK
content-type: application/json
Content-Length: 227
Date: ***, ** *** **** **:**:** GMT
Connection: keep-alive
keep-alive: timeout=5
[
{
"id": "************************************",
"title": "Hono Validator๐ฅ",
"content": "Hono Validator๐ฅ ใ่ฉฆใใฆใฟใพใใใ",
"createdAt": "***, ** *** **** **:**:** GMT",
"updatedAt": "***, ** *** **** **:**:** GMT",
"comments": []
}
]
ใณใกใณใใชใฏใจในใ
POST http://localhost:3000/comments/************************************ HTTP/1.1
Content-Type: application/json
Accept: application/json
{
"content": "Hono Takibi๐ฅ"
}
ๆ็จฟIDใใในใใฉใกใผใฟใซๆๅฎใใ็นๅฎใฎๆ็จฟใจใณใกใณใใๅๅพใใใใใฎใชใฏใจในใ
GET http://localhost:3000/posts/************************************ HTTP/1.1
Content-Type: application/json
Accept: application/json
ใฌในใใณใน
HTTP/1.1 200 OK
content-type: application/json
Content-Length: 443
Date: ***, ** *** **** **:**:** GMT
Connection: keep-alive
keep-alive: timeout=5
{
"id": "************************************",
"title": "Hono Takibi๐ฅ",
"content": "Hono Takibi๐ฅใฏใOpenAPIใฎๅฎ็พฉใใใZod OpenAPI Honoใฎใณใผใใ็ๆใใใใผใซใงใใ",
"createdAt": "***, ** *** **** **:**:** GMT",
"updatedAt": "***, ** *** **** **:**:** GMT",
"comments": [
{
"id": "************************************",
"postId": "************************************",
"content": "Hono Takibi๐ฅ",
"createdAt": "***, ** *** **** **:**:** GMT"
}
]
}
OpenAPI
ๅฎ็พฉใซHono Takibi
ใ้ฉ็จใใฆใฟใ
้็บใใชใใใ็ๆใใใโไปฅไธใฏใHono OpenAPI
ใ็จใใฆ้็บใใ็ๆใใใOpenAPI
ๅฎ็พฉใงใใ
openapi.json
{
"openapi": "3.1.0",
"info": { "title": "Hono API", "description": "Hono Valibot API", "version": "1.0.0" },
"tags": [
{ "name": "Hono", "description": "Hono API" },
{ "name": "Post", "description": "Post API" },
{ "name": "Comment", "description": "Comment API" }
],
"servers": [{ "url": "http://localhost:3000", "description": "Local Server" }],
"paths": {
"/": {
"get": {
"responses": {
"200": {
"description": "Hono Valibot๐ฅ",
"content": { "application/json": { "schema": { "type": "string" } } }
}
},
"operationId": "getIndex",
"tags": ["Hono"],
"description": "Hono Valibot๐ฅ"
}
},
"/posts": {
"post": {
"responses": {
"201": {
"description": "Post created successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"title": { "type": "string", "minLength": 1, "maxLength": 40 },
"content": { "type": "string", "minLength": 1, "maxLength": 140 },
"comments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"postId": { "type": "string", "format": "uuid" },
"content": { "type": "string", "minLength": 1, "maxLength": 140 },
"createdAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
}
},
"required": ["id", "postId", "content", "createdAt"]
}
},
"createdAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
},
"updatedAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
}
},
"required": ["id", "title", "content", "comments", "createdAt", "updatedAt"]
}
}
}
},
"400": {
"description": "Invalid request due to bad input.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
},
"500": {
"description": "Internal server error.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
}
},
"operationId": "postPosts",
"tags": ["Post"],
"description": "Create a post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"title": { "type": "string", "minLength": 1, "maxLength": 40 },
"content": { "type": "string", "minLength": 1, "maxLength": 140 }
},
"required": ["title", "content"]
}
}
}
}
},
"get": {
"responses": {
"200": {
"description": "Posts fetched successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"title": { "type": "string", "minLength": 1, "maxLength": 40 },
"content": { "type": "string", "minLength": 1, "maxLength": 140 },
"comments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"postId": { "type": "string", "format": "uuid" },
"content": { "type": "string", "minLength": 1, "maxLength": 140 },
"createdAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
}
},
"required": ["id", "postId", "content", "createdAt"]
}
},
"createdAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
},
"updatedAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
}
},
"required": ["id", "title", "content", "comments", "createdAt", "updatedAt"]
}
}
}
},
"400": {
"description": "Invalid request due to bad input.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
},
"500": {
"description": "Internal server error.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
}
},
"operationId": "getPosts",
"tags": ["Post"],
"description": "Get posts",
"parameters": [
{ "in": "query", "name": "page", "schema": { "type": "string" }, "required": true },
{ "in": "query", "name": "rows", "schema": { "type": "string" }, "required": true }
]
}
},
"/posts/{id}": {
"get": {
"responses": {
"200": {
"description": "Post fetched successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"title": { "type": "string", "minLength": 1, "maxLength": 40 },
"content": { "type": "string", "minLength": 1, "maxLength": 140 },
"comments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"postId": { "type": "string", "format": "uuid" },
"content": { "type": "string", "minLength": 1, "maxLength": 140 },
"createdAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
}
},
"required": ["id", "postId", "content", "createdAt"]
}
},
"createdAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
},
"updatedAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
}
},
"required": ["id", "title", "content", "comments", "createdAt", "updatedAt"]
}
}
}
},
"400": {
"description": "Invalid request due to bad input.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
},
"500": {
"description": "Internal server error.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
}
},
"operationId": "getPostsById",
"tags": ["Post"],
"description": "Get a post by ID.",
"parameters": [
{
"in": "path",
"name": "id",
"schema": { "type": "string", "format": "uuid" },
"required": true
}
]
},
"put": {
"responses": {
"204": { "description": "Post successfully updated." },
"400": {
"description": "Invalid request due to bad input.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
},
"500": {
"description": "Internal server error.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
}
},
"operationId": "putPostsById",
"tags": ["Post"],
"description": "Update a post by ID.",
"parameters": [
{
"in": "path",
"name": "id",
"schema": { "type": "string", "format": "uuid" },
"required": true
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"title": { "type": "string", "minLength": 1, "maxLength": 40 },
"content": { "type": "string", "minLength": 1, "maxLength": 140 }
},
"required": []
}
}
}
}
},
"delete": {
"responses": {
"204": { "description": "Post successfully deleted." },
"400": {
"description": "Invalid request due to bad input.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
},
"500": {
"description": "Internal server error.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
}
},
"operationId": "deletePostsById",
"tags": ["Post"],
"description": "Delete a post by ID.",
"parameters": [
{
"in": "path",
"name": "id",
"schema": { "type": "string", "format": "uuid" },
"required": true
}
]
}
},
"/comments/{id}": {
"post": {
"responses": {
"201": {
"description": "Comment created successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"postId": { "type": "string", "format": "uuid" },
"post": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"title": { "type": "string", "minLength": 1, "maxLength": 40 },
"content": { "type": "string", "minLength": 1, "maxLength": 140 },
"comments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"postId": { "type": "string", "format": "uuid" },
"content": { "type": "string", "minLength": 1, "maxLength": 140 },
"createdAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
}
},
"required": ["id", "postId", "content", "createdAt"]
}
},
"createdAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
},
"updatedAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
}
},
"required": ["id", "title", "content", "comments", "createdAt", "updatedAt"]
},
"content": { "type": "string", "minLength": 1, "maxLength": 140 },
"createdAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
}
},
"required": ["id", "postId", "post", "content", "createdAt"]
}
}
}
},
"400": {
"description": "Invalid request due to bad input.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
},
"500": {
"description": "Internal server error.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
}
},
"operationId": "postCommentsById",
"tags": ["Comment"],
"description": "Create a comment",
"parameters": [
{
"in": "path",
"name": "id",
"schema": { "type": "string", "format": "uuid" },
"required": true
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "content": { "type": "string", "minLength": 1, "maxLength": 140 } },
"required": ["content"]
}
}
}
}
},
"get": {
"responses": {
"200": {
"description": "Comment fetched successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"postId": { "type": "string", "format": "uuid" },
"post": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"title": { "type": "string", "minLength": 1, "maxLength": 40 },
"content": { "type": "string", "minLength": 1, "maxLength": 140 },
"comments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"postId": { "type": "string", "format": "uuid" },
"content": { "type": "string", "minLength": 1, "maxLength": 140 },
"createdAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
}
},
"required": ["id", "postId", "content", "createdAt"]
}
},
"createdAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
},
"updatedAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
}
},
"required": ["id", "title", "content", "comments", "createdAt", "updatedAt"]
},
"content": { "type": "string", "minLength": 1, "maxLength": 140 },
"createdAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$"
}
},
"required": ["id", "postId", "post", "content", "createdAt"]
}
}
}
},
"400": {
"description": "Invalid request due to bad input.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
},
"404": {
"description": "Comment not found.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
},
"500": {
"description": "Internal server error.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
}
},
"operationId": "getCommentsById",
"tags": ["Comment"],
"description": "Get a comment by ID.",
"parameters": [
{
"in": "path",
"name": "id",
"schema": { "type": "string", "format": "uuid" },
"required": true
}
]
},
"put": {
"responses": {
"204": { "description": "Comment successfully updated." },
"400": {
"description": "Invalid request due to bad input.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
},
"500": {
"description": "Internal server error.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
}
},
"operationId": "putCommentsById",
"tags": ["Comment"],
"description": "Update a comment by ID.",
"parameters": [
{
"in": "path",
"name": "id",
"schema": { "type": "string", "format": "uuid" },
"required": true
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "content": { "type": "string", "minLength": 1, "maxLength": 140 } },
"required": ["content"]
}
}
}
}
},
"delete": {
"responses": {
"204": { "description": "Comment successfully deleted." },
"400": {
"description": "Invalid request due to bad input.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
},
"500": {
"description": "Internal server error.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
}
},
"operationId": "deleteCommentsById",
"tags": ["Comment"],
"description": "Delete a comment by ID.",
"parameters": [
{
"in": "path",
"name": "id",
"schema": { "type": "string", "format": "uuid" },
"required": true
}
]
}
}
},
"components": { "schemas": {} }
}
โไปฅไธใ็ๆใใใใณใผใใงใใ
import { createRoute, z } from '@hono/zod-openapi'
export const getRoute = createRoute({
tags: ['Hono'],
method: 'get',
path: '/',
description: 'Hono Valibot๐ฅ',
responses: {
200: { description: 'Hono Valibot๐ฅ', content: { 'application/json': { schema: z.string() } } },
},
})
export const postPostsRoute = createRoute({
tags: ['Post'],
method: 'post',
path: '/posts',
description: 'Create a post',
request: {
body: {
required: false,
content: {
'application/json': {
schema: z.object({
title: z.string().min(1).max(40),
content: z.string().min(1).max(140),
}),
},
},
},
},
responses: {
201: {
description: 'Post created successfully',
content: {
'application/json': {
schema: z.object({
id: z.string().uuid(),
title: z.string().min(1).max(40),
content: z.string().min(1).max(140),
comments: z.array(
z.object({
id: z.string().uuid(),
postId: z.string().uuid(),
content: z.string().min(1).max(140),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
}),
),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
updatedAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
}),
},
},
},
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
},
})
export const getPostsRoute = createRoute({
tags: ['Post'],
method: 'get',
path: '/posts',
description: 'Get posts',
request: { query: z.object({ page: z.string(), rows: z.string() }) },
responses: {
200: {
description: 'Posts fetched successfully',
content: {
'application/json': {
schema: z.object({
id: z.string().uuid(),
title: z.string().min(1).max(40),
content: z.string().min(1).max(140),
comments: z.array(
z.object({
id: z.string().uuid(),
postId: z.string().uuid(),
content: z.string().min(1).max(140),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
}),
),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
updatedAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
}),
},
},
},
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
},
})
export const getPostsIdRoute = createRoute({
tags: ['Post'],
method: 'get',
path: '/posts/{id}',
description: 'Get a post by ID.',
request: { params: z.object({ id: z.string().uuid() }) },
responses: {
200: {
description: 'Post fetched successfully',
content: {
'application/json': {
schema: z.object({
id: z.string().uuid(),
title: z.string().min(1).max(40),
content: z.string().min(1).max(140),
comments: z.array(
z.object({
id: z.string().uuid(),
postId: z.string().uuid(),
content: z.string().min(1).max(140),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
}),
),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
updatedAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
}),
},
},
},
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
},
})
export const putPostsIdRoute = createRoute({
tags: ['Post'],
method: 'put',
path: '/posts/{id}',
description: 'Update a post by ID.',
request: {
body: {
required: false,
content: {
'application/json': {
schema: z
.object({ title: z.string().min(1).max(40), content: z.string().min(1).max(140) })
.partial(),
},
},
},
params: z.object({ id: z.string().uuid() }),
},
responses: {
204: { description: 'Post successfully updated.' },
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
},
})
export const deletePostsIdRoute = createRoute({
tags: ['Post'],
method: 'delete',
path: '/posts/{id}',
description: 'Delete a post by ID.',
request: { params: z.object({ id: z.string().uuid() }) },
responses: {
204: { description: 'Post successfully deleted.' },
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
},
})
export const postCommentsIdRoute = createRoute({
tags: ['Comment'],
method: 'post',
path: '/comments/{id}',
description: 'Create a comment',
request: {
body: {
required: false,
content: {
'application/json': { schema: z.object({ content: z.string().min(1).max(140) }) },
},
},
params: z.object({ id: z.string().uuid() }),
},
responses: {
201: {
description: 'Comment created successfully',
content: {
'application/json': {
schema: z.object({
id: z.string().uuid(),
postId: z.string().uuid(),
post: z.object({
id: z.string().uuid(),
title: z.string().min(1).max(40),
content: z.string().min(1).max(140),
comments: z.array(
z.object({
id: z.string().uuid(),
postId: z.string().uuid(),
content: z.string().min(1).max(140),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
}),
),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
updatedAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
}),
content: z.string().min(1).max(140),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
}),
},
},
},
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
},
})
export const getCommentsIdRoute = createRoute({
tags: ['Comment'],
method: 'get',
path: '/comments/{id}',
description: 'Get a comment by ID.',
request: { params: z.object({ id: z.string().uuid() }) },
responses: {
200: {
description: 'Comment fetched successfully',
content: {
'application/json': {
schema: z.object({
id: z.string().uuid(),
postId: z.string().uuid(),
post: z.object({
id: z.string().uuid(),
title: z.string().min(1).max(40),
content: z.string().min(1).max(140),
comments: z.array(
z.object({
id: z.string().uuid(),
postId: z.string().uuid(),
content: z.string().min(1).max(140),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
}),
),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
updatedAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
}),
content: z.string().min(1).max(140),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/),
}),
},
},
},
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
404: {
description: 'Comment not found.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
},
})
export const putCommentsIdRoute = createRoute({
tags: ['Comment'],
method: 'put',
path: '/comments/{id}',
description: 'Update a comment by ID.',
request: {
body: {
required: false,
content: {
'application/json': { schema: z.object({ content: z.string().min(1).max(140) }) },
},
},
params: z.object({ id: z.string().uuid() }),
},
responses: {
204: { description: 'Comment successfully updated.' },
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
},
})
export const deleteCommentsIdRoute = createRoute({
tags: ['Comment'],
method: 'delete',
path: '/comments/{id}',
description: 'Delete a comment by ID.',
request: { params: z.object({ id: z.string().uuid() }) },
responses: {
204: { description: 'Comment successfully deleted.' },
400: {
description: 'Invalid request due to bad input.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
500: {
description: 'Internal server error.',
content: { 'application/json': { schema: z.object({ message: z.string() }) } },
},
},
})
ใใใใซ
โHono OpenAPI
ใจValibot
ใ่ฉฆใใฆใฟใพใใใไปใซใใZod
ใArkType
ใTypeBox
ใชใฉใ้ธๆ่ขใใใใฎใงใใใใใ่ฉฆใใฆใฟใใใงใใ
Discussion