Skip to content

Instantly share code, notes, and snippets.

@noook
Created October 30, 2024 15:47
Show Gist options
  • Select an option

  • Save noook/dcae2eef50a9e9181c1089791dc61bfd to your computer and use it in GitHub Desktop.

Select an option

Save noook/dcae2eef50a9e9181c1089791dc61bfd to your computer and use it in GitHub Desktop.
// Import h3 as npm dependency
import { createApp, createRouter, defineEventHandler, getRouterParam, sendError, createError, readBody, readValidatedBody, setResponseStatus, sendNoContent, getQuery, getValidatedQuery, H3Event, getRequestHeader } from "h3";
import { z } from 'zod'
import type { Prettify } from './utils'
// Create an app instance
export const app = createApp();
// Create a new router and register it in app
const router = createRouter();
app.use(router);
interface Identifiable {
id: number
}
// interface IBook extends Identifiable {
// title: string
// author: string
// releaseYear: number
// genres: string[]
// rating: number
// }
const bookSchema = z.object({
title: z.string(),
author: z.string(),
releaseYear: z.number(),
// genres: z.array(z.string())
genres: z.string().array()
.min(1, 'Le livre doit avoir au moins un genre'),
rating: z.number()
.min(1, 'Rating doit être supérieur à 1')
.max(5, 'Rating doit être inférieur à 5'),
})
// Intersection des deux types
type Book = Identifiable & z.infer<typeof bookSchema>
const books: Book[] = [
{ id: 1, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', releaseYear: 1925, genres: ['novel'], rating: 5 },
{ id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', releaseYear: 1960, genres: ['novel'], rating: 5 },
{ id: 3, title: '1984', author: 'George Orwell', releaseYear: 1949, genres: ['dystopian', 'science fiction'], rating: 5 },
{ id: 4, title: 'Pride and Prejudice', author: 'Jane Austen', releaseYear: 1813, genres: ['romance', 'novel'], rating: 4 },
{ id: 5, title: 'The Catcher in the Rye', author: 'J.D. Salinger', releaseYear: 1951, genres: ['novel'], rating: 4 },
{ id: 6, title: 'The Hobbit', author: 'J.R.R. Tolkien', releaseYear: 1937, genres: ['fantasy'], rating: 5 },
]
function serializeText(book: Book): string {
return `${book.title} by ${book.author}, released in ${book.releaseYear}`
}
function getRequestedContentType(event: H3Event) {
const mimeType = getRequestHeader(event, 'Accept')
if (mimeType === 'text/plain') {
return 'text/plain'
}
return 'application/json'
}
// Add a new route that matches GET requests to / path
router.get(
"/",
defineEventHandler((event) => {
return { message: "⚡️ Tadaa Welcome home" };
}),
)
const paginationQuerySchema = z.object({
page: z.coerce.number()
.min(1, 'Page doit être supérieur à 1')
.default(1),
limit: z.coerce.number()
.min(2)
.max(10)
.default(3),
})
// ?page=&1page=2
// => { page: ["1", "2"] }
// ?page=1
// => { page: "1" }
router.get(
'/books',
defineEventHandler(async event => {
const { page, limit } = await getValidatedQuery(event, paginationQuerySchema.parse)
const maxPage = Math.ceil(books.length / limit)
if (page > maxPage) {
return sendError(event, createError({
statusCode: 400,
statusMessage: `Page ${page} is out of range (max: ${maxPage})`
}))
}
const offset = (page - 1) * limit
const booksSlice = books.slice(offset, page * limit)
if (getRequestedContentType(event) === 'text/plain') {
return booksSlice.map(book => serializeText(book)).join('\n')
}
return {
data: booksSlice,
page,
limit,
maxPage,
total: books.length,
}
})
)
router.get(
'/books/:id',
defineEventHandler(event => {
const id = getRouterParam(event, 'id')
if (!id) {
return sendError(event, createError({
statusCode: 400,
message: 'Missing id parameter'
}))
}
const idNumber = Number(id)
const book = books.find(book => book.id === idNumber)
if (!book) {
return sendError(event, createError({
statusCode: 404,
message: 'Book not found'
}))
}
if (getRequestedContentType(event) === 'text/plain') {
return serializeText(book)
}
return book
}),
)
router.post(
'/books',
defineEventHandler(async event => {
// const body = await readValidatedBody(event, data => bookSchema.parse(data))
// 400 Bad request = body not compliant with schema
const body = await readValidatedBody(event, bookSchema.parse)
const nextId = books.at(-1)!.id + 1
const newBook = { id: nextId, ...body }
books.push(newBook)
setResponseStatus(event, 201)
if (getRequestedContentType(event) === 'text/plain') {
return serializeText(newBook)
}
return newBook
}),
)
router.put(
'/books/:id',
defineEventHandler(async event => {
const id = getRouterParam(event, 'id')
if (!id) {
return sendError(event, createError({
statusCode: 400,
message: 'Missing id parameter'
}))
}
const idNumber = Number(id)
const bookIdx = books.findIndex(book => book.id === idNumber)
if (bookIdx === -1) {
return sendError(event, createError({
statusCode: 404,
message: 'Book not found'
}))
}
const body = await readValidatedBody(event, bookSchema.parse)
const updatedBook = { id: idNumber, ...body }
books[bookIdx] = updatedBook
if (getRequestedContentType(event) === 'text/plain') {
return serializeText(updatedBook)
}
return updatedBook
}),
)
.delete(
'/books/:id',
defineEventHandler(event => {
const id = getRouterParam(event, 'id')
if (!id) {
return sendError(event, createError({
statusCode: 400,
message: 'Missing id parameter'
}))
}
const idNumber = Number(id)
const bookIdx = books.findIndex(book => book.id === idNumber)
if (bookIdx === -1) {
return sendError(event, createError({
statusCode: 404,
message: 'Book not found'
}))
}
books.splice(bookIdx, 1)
// setResponseStatus(event, 204)
// sendNoContent(event)
// return null
return sendNoContent(event)
}),
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment