JSON REST API

JSON:API Library Comparison

This document compares json-rest-api with other JSON:API libraries in the Node.js ecosystem, highlighting key differences and advantages.

Overview

json-rest-api is a full-featured, plugin-based JSON:API implementation built on the hooked-api framework. Unlike other solutions, it offers a unique combination of compliance, flexibility, and developer experience.

Feature Comparison Matrix

Feature json-rest-api json-api-serializer nestjs-json-api @jsonapi/server fortune.js
Core Features          
Full JSON:API 1.1 compliance ⚠️ (serialization only) ⚠️
Simplified mode (plain objects)
Plugin architecture N/A ⚠️
Framework agnostic ❌ (NestJS only)
Database Support          
Knex.js integration
Multiple databases ✅ (15+) N/A ⚠️ (via TypeORM) ⚠️
Migrations N/A ⚠️
Query optimization N/A ⚠️
Relationships          
BelongsTo/HasMany
Many-to-Many ⚠️ ⚠️
Polymorphic ⚠️
Deep includes ⚠️ ⚠️
Advanced Features          
File uploads
WebSocket support
Bulk operations
Positioning/sorting
Authentication ⚠️
Developer Experience          
TypeScript support
Active maintenance ✅ (2025) ✅ (2024) ✅ (2024) 🟡 (2022) ❌ (2022)
Documentation ⚠️ ⚠️
Test coverage ✅ (95%+) ⚠️ ⚠️

Detailed Comparisons

vs json-api-serializer

json-api-serializer is a serialization-only library that focuses on converting JavaScript objects to/from JSON:API format.

Key Differences:

// json-api-serializer (what you write)
const JSONAPISerializer = require('jsonapi-serializer').Serializer;
const BookSerializer = new JSONAPISerializer('books', {
  attributes: ['title', 'author'],
  author: { ref: 'id', included: true }
});

// Plus you need to write:
// - Express routes
// - Database queries
// - Relationship loading
// - Error handling
// - Query parsing

// json-rest-api (complete solution)
import { Api } from 'hooked-api';
import { RestApiPlugin, RestApiKnexPlugin } from 'json-rest-api';

const api = new Api({ name: 'bookstore-api', version: '1.0.0' });
await api.use(RestApiPlugin, { publicBaseUrl: '/api/v1' });
await api.use(RestApiKnexPlugin, { knex });

await api.addResource('books', {
  schema: {
    id: { type: 'id' },
    title: { type: 'string', required: true },
    author_id: { type: 'id', belongsTo: 'authors', as: 'author' }
  },
  relationships: {
    author: { belongsTo: 'authors' }
  }
});
await api.resources.books.createKnexTable();
// That's it - routing, DB, serialization all handled

vs nestjs-json-api

nestjs-json-api provides JSON:API support specifically for NestJS applications using decorators.

Key Differences:

// nestjs-json-api (NestJS only)
@JsonApiResource('books')
export class BookEntity {
  @JsonApiAttribute()
  title: string;
  
  @JsonApiBelongsTo('authors')
  author: AuthorEntity;
}

// json-rest-api (framework agnostic)
import { Api } from 'hooked-api';
import { RestApiPlugin, ExpressPlugin, SocketIOPlugin } from 'json-rest-api';

const api = new Api({ name: 'books-api', version: '1.0.0' });
await api.use(RestApiPlugin);
await api.use(ExpressPlugin);    // HTTP transport
await api.use(SocketIOPlugin);   // WebSocket transport

await api.addResource('books', {
  schema: {
    id: { type: 'id' },
    title: { type: 'string', required: true },
    author_id: { type: 'id', belongsTo: 'authors', as: 'author' }
  },
  relationships: {
    author: { belongsTo: 'authors' }
  }
});
// Works with Express, Fastify, or any framework - plus WebSockets!

vs @jsonapi/server

@jsonapi/server is a lightweight JSON:API framework that provides basic compliance.

Key Differences:

vs fortune.js

fortune.js is a hypermedia API framework with optional JSON:API support.

Key Differences:

Unique json-rest-api Features

1. Simplified Mode

No other library offers dual-mode operation:

// JSON:API mode (for HTTP clients)
const response = await fetch('/api/books', {
  method: 'POST',
  headers: { 'Content-Type': 'application/vnd.api+json' },
  body: JSON.stringify({
    data: {
      type: 'books',
      attributes: { title: 'My Book' },
      relationships: {
        author: { data: { type: 'authors', id: '1' } }
      }
    }
  })
});
// Returns: { data: { type: 'books', id: '1', attributes: {...}, relationships: {...} } }

// Simplified mode (for internal/programmatic use)
const book = await api.resources.books.post({
  title: 'My Book',
  author_id: 1
});
// Returns: { id: '1', title: 'My Book', author_id: 1 }

// Configure both modes simultaneously
await api.use(RestApiPlugin, {
  simplifiedApi: true,      // Programmatic calls use plain objects
  simplifiedTransport: false // HTTP endpoints use JSON:API format
});

2. Plugin Architecture

Unmatched flexibility through composition:

import { Api } from 'hooked-api';
import { 
  RestApiPlugin, 
  RestApiKnexPlugin, 
  ExpressPlugin,
  CorsPlugin,
  JwtAuthPlugin,
  FileHandlingPlugin,
  BulkOperationsPlugin,
  SocketIOPlugin
} from 'json-rest-api';

const api = new Api({ name: 'full-featured-api', version: '1.0.0' });

// Core functionality
await api.use(RestApiPlugin, { publicBaseUrl: '/api/v1' });
await api.use(RestApiKnexPlugin, { knex: connection });

// Transport layer
await api.use(ExpressPlugin, { mountPath: '/api' });

// Cross-cutting concerns
await api.use(CorsPlugin, { 
  origin: ['https://app.example.com'],
  credentials: true 
});
await api.use(JwtAuthPlugin, { 
  secret: process.env.JWT_SECRET,
  expiresIn: '24h' 
});

// Advanced features
await api.use(FileHandlingPlugin, { 
  storage: 's3',
  bucket: 'my-uploads' 
});
await api.use(BulkOperationsPlugin, { 
  maxBulkOperations: 100 
});
await api.use(SocketIOPlugin, { 
  cors: { origin: "https://app.example.com" } 
});

// Each plugin extends the API with new capabilities
// All work together seamlessly through the plugin system

3. Advanced Relationship Handling

Superior polymorphic relationship support:

// Define polymorphic resource
await api.addResource('comments', {
  schema: {
    id: { type: 'id' },
    content: { type: 'string', required: true },
    // Polymorphic foreign keys
    commentable_type: { type: 'string', required: true },
    commentable_id: { type: 'id', required: true }
  },
  relationships: {
    commentable: {
      belongsToPolymorphic: {
        types: ['posts', 'videos', 'articles'],
        typeField: 'commentable_type',
        idField: 'commentable_id'
      }
    }
  }
});

// Parent resources define reverse relationship
await api.addResource('posts', {
  relationships: {
    comments: { hasMany: 'comments', via: 'commentable' }
  }
});

// Automatically handles type resolution and eager loading
const comment = await api.resources.comments.get({
  id: '1',
  queryParams: { include: ['commentable'] } // Works with any type
});

// Create polymorphic relationships
await api.resources.comments.post({
  content: 'Great post!',
  commentable_type: 'posts',
  commentable_id: 1
});

4. N+1 Query Prevention

Automatic optimization that competitors lack:

// Define resources with nested relationships
await api.addResource('countries', {
  relationships: {
    publishers: { hasMany: 'publishers', foreignKey: 'country_id' }
  }
});

await api.addResource('publishers', {
  relationships: {
    books: { hasMany: 'books', foreignKey: 'publisher_id' },
    country: { belongsTo: 'countries' }
  }
});

await api.addResource('books', {
  relationships: {
    authors: { hasMany: 'authors', through: 'book_authors', foreignKey: 'book_id', otherKey: 'author_id' },
    publisher: { belongsTo: 'publishers' }
  }
});

// This generates optimized queries automatically
const books = await api.resources.books.query({
  queryParams: {
    include: [
      'authors',                    // Include book authors
      'publisher',                  // Include publisher
      'publisher.country'           // Include publisher's country (nested)
    ],
    page: { size: 20, number: 1 }
  }
});

// json-rest-api automatically:
// 1. Identifies all needed relationships
// 2. Generates optimal JOIN queries  
// 3. Batches relationship loading
// 4. Returns properly structured JSON:API response
//
// Instead of 1 + N + N*M queries, this executes only ~4 optimized queries
// regardless of how many books, publishers, or authors are returned

When to Choose json-rest-api

Choose json-rest-api when you need:

Consider alternatives when:

Migration Guide

From json-api-serializer

// Before (json-api-serializer + custom code)
app.get('/books', async (req, res) => {
  const books = await db.select('*').from('books');
  const serialized = new BookSerializer(books);
  res.json(serialized);
});

// After (json-rest-api)
import { Api } from 'hooked-api';
import { RestApiPlugin, RestApiKnexPlugin, ExpressPlugin } from 'json-rest-api';

const api = new Api({ name: 'books-api', version: '1.0.0' });
await api.use(RestApiPlugin);
await api.use(RestApiKnexPlugin, { knex: db });
await api.use(ExpressPlugin, { mountPath: '/api' });

await api.addResource('books', {
  schema: {
    id: { type: 'id' },
    title: { type: 'string', required: true }
  }
});

const app = express();
app.use(api.http.express.router);
// All routes, serialization, and DB queries handled

From nestjs-json-api

// Before (NestJS with decorators)
@Controller('books')
export class BooksController {
  @Get()
  @JsonApiResponse(BookEntity)
  findAll() { /* custom implementation needed */ }
  
  @Get(':id')
  @JsonApiResponse(BookEntity)
  findOne(@Param('id') id: string) { /* custom implementation needed */ }
}

// After (json-rest-api - zero boilerplate)
import { Api } from 'hooked-api';
import { RestApiPlugin, RestApiKnexPlugin } from 'json-rest-api';

const api = new Api({ name: 'books-api', version: '1.0.0' });
await api.use(RestApiPlugin);
await api.use(RestApiKnexPlugin, { knex });

await api.addResource('books', {
  schema: {
    id: { type: 'id' },
    title: { type: 'string', required: true },
    published: { type: 'boolean', default: false }
  },
  readScopes: {
    published: (query) => query.where('published', true)
  }
});
// GET /books, GET /books/:id, POST /books, etc. all automatically available

Conclusion

json-rest-api stands out as the most comprehensive JSON:API solution for Node.js:

  1. Complete Solution: Unlike serialization-only libraries, it handles everything
  2. Flexible Architecture: Plugin-based design beats monolithic frameworks
  3. Superior Features: Simplified mode, file uploads, WebSockets, and more
  4. Active Development: Regular updates and community support
  5. Production Ready: Battle-tested with extensive test coverage

For teams building JSON:API services in 2025, json-rest-api offers the best combination of compliance, features, and developer experience.