CRUD operations

Here is a simple example of creating RESTful API with all the CRUD operations on posts

We want this API to be JSON only. So first let's set default Content-Type for our router (Read more about it at Routing) to application/json:

const router = new Router({
    type: 'application/json'
})

Setting default Content-Type will make all the successful responses rendered as JSON automatically. Next we're going to make all the errors render as JSON:

const errorHandler = (_: Context, error: HTTPError) => {
    return new HTTPResponse({
        status: error.code,
        type: 'application/json',
        body: { ok: false, message: error.message }
    })
}

const app = new Application()
app.handleErrors(errorHandler)

Now we just need to declare all the route handlers, which can be seen in the full code snippet below:

// crud.ts
import {
    Application,
    HTTPResponse,
    HTTPError,
    HTTPStatus,
    Context,
    Router,
    BadRequestError,
    NotFoundError
} from 'jsr:@sequoia/sequoia'

// Custom error handler to display errors as JSON
const errorHandler = (_: Context, error: HTTPError) => {
    return new HTTPResponse({
        status: error.code,
        type: 'application/json',
        body: { ok: false, message: error.message }
    })
}

const app = new Application()
app.handleErrors(errorHandler)
const router = new Router({
    type: 'application/json'
})

interface Post {
    id: number
    text: string
}

let posts: Post[] = []

const isValid = (id: number) => !isNaN(id) && Number.isInteger(id) && id > 0

router.GET('/posts', () => new HTTPResponse({ body: posts }))

router.GET('/posts/:id', (context: Context<{ id?: string }>) => {
    const { id } = context.request.params
    if (!id || !isValid(+id)) {
        throw new BadRequestError('Please specify a correct post id')
    }

    const post = posts.find((post) => post.id === +id)
    if (!post) {
        throw new NotFoundError('Post with specified id does not exist')
    }
    return new HTTPResponse({ body: post })
})

router.POST('/posts', async (context) => {
    const { id, text } = (await context.request.json()) as Partial<Post>
    if (!id || !text) {
        throw new BadRequestError('Fields "id" and "text" are required')
    }
    if (!isValid(id)) {
        throw new BadRequestError('Field "id" must be an integer')
    }
    if (posts.find((post) => post.id === +id)) {
        throw new BadRequestError('A post with specified id already exists')
    }

    posts.push({ id, text })
    return new HTTPResponse({ status: HTTPStatus.CREATED, body: { ok: true } })
})

router.PUT('/posts/:id', async (context: Context<{ id?: string }>) => {
    const { id } = context.request.params
    const { text } = (await context.request.json()) as { text?: string }
    if (!id || !isValid(+id) || !text) {
        throw new BadRequestError('Please specify a correct post id and text')
    }

    const index = posts.findIndex((post) => post.id === +id)
    if (index === -1) {
        throw new NotFoundError('Post with specified id does not exist')
    }
    posts[index] = {
        text,
        id: posts[index].id
    }
    return new HTTPResponse({ body: { ok: true } })
})

router.DELETE('/posts/:id', (context: Context<{ id?: string }>) => {
    const { id } = context.request.params
    if (!id || !isValid(+id)) {
        throw new BadRequestError('Please specify a correct post id')
    }

    const post = posts.find((post) => post.id === +id)
    if (!post) {
        throw new NotFoundError('Post with specified id does not exist')
    }
    posts = posts.filter((post) => post.id === +id)
    return new HTTPResponse({ body: { ok: true } })
})

app.useRouter(router)
await app.listen({ port: 8000 })

Run it with deno run --allow-net crud.ts

Or visit our example hosted at Deno Deploy