Taxum Integration
This package provides layers, error handling and request extractors for integrating @jsonapi-serde/server
seamlessly with Taxum. It simplifies handling JSON:API content negotiation, request parsing, and error formatting in your Taxum apps.
Installation
$ npm add @jsonapi-serde/integration-taxum
$ pnpm add @jsonapi-serde/integration-taxum
$ yarn add @jsonapi-serde/integration-taxum
$ bun add @jsonapi-serde/integration-taxum
Initialization
In your entry file, you should import the augmentation module:
import "@jsonapi-serde/integration-taxum/augment";
This augments JsonApiDocument
and JsonApiError
to implement ToHttpResponse
, making them directly usable as handler return values.
// works directly as a response
return new JsonApiDocument({ /* ... */ });
Layers and Handlers
A Taxum layer that parses the Accept header, determines acceptable JSON:API media types, and validates that responses match those types.
Register it late in your router's layer stack, ideally after compression, since it may still generate responses.
import { jsonApiMediaTypesLayer } from "@jsonapi-serde/integration-taxum";
router.layer(jsonApiMediaTypesLayer);
Error Handling
The JSON:API error handler is registered with router.errorHandler():
import { jsonApiErrorHandler } from "@jsonapi-serde/integration-taxum";
router.errorHandler(jsonApiErrorHandler({
logError: (err, exposed) => {
if (!exposed) {
console.error(err);
}
}
}));
This handler formats thrown errors into JSON:API error documents. The logError
option works the same way as in the Koa integration.
Fallback Handlers
The package exports notFoundHandler and methodNotAllowedHandler:
import { notFoundHandler, methodNotAllowedHandler } from "@jsonapi-serde/integration-taxum";
router
.fallback(notFoundHandler)
.methodNotAllowedFallback(methodNotAllowedHandler);
These return proper JSON:API error responses for missing routes or unsupported methods.
Extractors
Extractors let you plug JSON:API request parsing directly into your route handlers.
jsonApiQuery(parse: QueryParser)
Extracts query parameters using a JSON:API QueryParser
.
import { jsonApiQuery } from "@jsonapi-serde/integration-taxum";
import { createQueryParser } from "@jsonapi-serde/server/request";
const parseQuery = createQueryParser({
include: {
allowed: ["author", "comments.author"],
},
});
router.route("/articles", m.get([jsonApiQuery(parseQuery)], (query) => {
return listArticles(query);
}));
jsonApiResource(options, idPathParam?)
Extracts and parses a JSON:API resource from the request body.
If idPathParam
is provided, it will validate that the ID in the body matches the path parameter.
import { jsonApiResourceRequest } from "@jsonapi-serde/integration-taxum";
import { z } from "zod";
const articleAttributesSchema = z.object({
title: z.string(),
content: z.string()
});
router.route(
"/articles",
m.post(
[
jsonApiResourceRequest({
type: "article",
attributesSchema: articleAttributesSchema
})
],
(resource) => {
const article = saveArticle(resource.attributes);
return serialize(article);
},
),
);
Other extractors
jsonApiRelationship(type, idSchema?)
: parses a to-one relationship from the body.jsonApiRelationships(type, idSchema?)
: parses a to-many relationship from the body.
Full Example
import "@jsonapi-serde/integration-taxum/augment";
import { Router, m } from "@taxum/core/routing";
import { serve } from "@taxum/core/server";
import { z } from "zod";
import {
jsonApiMediaTypesLayer,
jsonApiErrorHandler,
notFoundHandler,
methodNotAllowedHandler,
jsonApiQuery,
jsonApiResourceRequest,
} from "@jsonapi-serde/integration-taxum";
import {
SerializeBuilder,
createQueryParser,
} from "@jsonapi-serde/server";
// Populate your entity serializers
const serialize = SerializeBuilder.new().build();
const articleAttributesSchema = z.object({
title: z.string(),
content: z.string(),
});
const parseQuery = createQueryParser({
include: { allowed: ["author", "comments.author"] },
sort: { allowed: ["title", "createdAt"] },
});
const router = new Router()
.route(
"/articles",
m.get([jsonApiQuery(parseQuery)], (query) => {
const articles = listArticles(query);
return serialize(articles);
}),
)
.route(
"/articles",
m.post(
[
jsonApiResourceRequest({
type: "article",
attributesSchema: articleAttributesSchema,
}),
],
(resource) => {
const savedArticle = saveArticle(resource.attributes);
return serialize({
type: "article",
id: savedArticle.id,
attributes: resource.attributes,
});
},
),
)
.errorHandler(jsonApiErrorHandler({
logError: (err, exposed) => {
if (!exposed) {
console.error(err);
}
}
}))
.fallback(notFoundHandler)
.methodNotAllowedFallback(methodNotAllowedHandler)
.layer(jsonApiMediaTypesLayer);
serve(router, { port: 3000 });