Serve a spec-compliant API from your existing Lucid models with a few lines per endpoint. Includes, sparse fieldsets, sorting, filtering, pagination, error documents, content negotiation and full write support are all handled for you.
// A complete JSON:API endpoint:
async index({ jsonApi }: HttpContext) {
const articles = await jsonApi.query(Article).paginate(...jsonApi.page)
return jsonApi.render(articles)
}New to JSON:API itself? Start with What is JSON:API?
node ace add @evoactivity/jsonapi-adonisThis installs the package and configures it: it creates config/jsonapi.ts, then registers the provider, the jsonApi named middleware and the generator commands.
Requirements: AdonisJS v7 (@adonisjs/core ^7), Lucid v22 (@adonisjs/lucid ^22).
1. Generate a resource and controllers for one of your models:
node ace make:jsonapi:resource article --relationships --routesThis creates app/resources/article_resource.ts and the controllers, and registers the routes. You can also write them by hand, see the reference. Register the resource in config/jsonapi.ts:
export default defineConfig({
resources: [() => import('#resources/article_resource')],
})Note
Resource classes are optional. Every model serializes automatically with its type, attributes and relationships derived from Lucid metadata, so a controller alone works fine: node ace make:jsonapi:controller article generates just the controllers, and there's nothing to register in the config. Write a resource class when you want to customize the output, see Customizing a resource.
2. That's it. Make a request:
GET /api/v1/articles/1?include=author,tags
You get a complete JSON:API document: the article as primary data, the author and tags in included (deduplicated), relationship linkage, self and related links, and the application/vnd.api+json content type. The ?include= paths were validated and preloaded in one pass. Unknown paths get a 400, as the spec requires, and there are no N+1 queries.
The generated controller is plain AdonisJS. jsonApi.query(Article) is literally Article.query() with the request's include, sort and filter parameters applied, and you can chain .where(), scopes and .paginate() as usual:
export default class ArticlesController {
async index({ jsonApi }: HttpContext) {
const articles = await jsonApi.query(Article).paginate(...jsonApi.page)
return jsonApi.render(articles)
}
async show({ jsonApi, params }: HttpContext) {
const article = await jsonApi.query(Article).where('id', params.id).firstOrFail()
return jsonApi.render(article)
}
async store({ jsonApi }: HttpContext) {
const input = await jsonApi.deserialize(Article)
const article = await Article.create(await createArticleValidator.validate(input.attributes))
await jsonApi.syncToMany(article, input.toMany)
return jsonApi.render(article, { status: 201 })
}
}3. Render errors as JSON:API documents. One branch in your exception handler:
// app/exceptions/handler.ts
import { renderJsonApiError } from '@evoactivity/jsonapi-adonis'
async handle(error: unknown, ctx: HttpContext) {
if (ctx.jsonApi.handlesErrors()) {
return renderJsonApiError(error, ctx, this.debug)
}
return super.handle(error, ctx)
}Models without a resource class serialize automatically, with the type, attributes and relationships derived from Lucid metadata. You only write resource classes to customize.
| Guide | Covers |
|---|---|
| What is JSON:API? | The ideas behind the spec |
| Reading data | Resources and types, customizing, include, sparse fieldsets, sorting, pagination, filtering |
| Writing data | Create, update and delete from JSON:API documents, relationship endpoints |
| Links | Route-driven URL generation, API versioning, casing |
| Errors & negotiation | Error documents, handlesErrors(), media type rules |
| Low-level building blocks | Serializing outside a request: commands, jobs, tests |
| Reference | The jsonApi helper API, configuration, generators, roadmap |
examples/blog is a complete AdonisJS application (articles, comments, tags, users) exercising every feature. The same resources are mounted under /api/v1 and /api/v2 to demonstrate versioned links.
pnpm install
cd examples/blog
node ace migration:run
node ace db:seed # demo data: authors, articles, tags, comments
node ace serve --watch
curl 'localhost:3333/api/v1/articles?include=author,tags'pnpm test # package unit tests (no database needed)
pnpm test:example # example app functional suite (spec compliance, writes, links)
pnpm test:all # bothMIT