npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@edward644/jsonapi

v2.0.1

Published

`@edward644/jsonapi` is a TypeScript library for building JSON:API-compliant responses for [express.js](https://expressjs.com/). It provides a flexible and extensible way to construct JSON:API documents, including support for nested relationships, error h

Downloads

46

Readme

JsonAPI

@edward644/jsonapi is a TypeScript library for building JSON:API-compliant responses for express.js. It provides a flexible and extensible way to construct JSON:API documents, including support for nested relationships, error handling, and metadata.

Features

  • JSON:API Specification: Adheres to the JSON:API 1.1 specification.
  • Nested Includes: Dynamically fetch and include related resources using dot-separated paths.
  • Error Handling: Easily add and manage errors in the response.
  • Customizable: Define custom getters for fetching related resources.
  • TypeScript Support: Fully typed for better development experience.

Installation

Install the package via npm:

npm install @edward644/jsonapi

Usage

Basic Example

import { Builder } from '@edward644/jsonapi';
import { Config, Item, ResourceIdentifier } from '@edward644/jsonapi/@types';

// Example data
const article = {
  type: 'articles',
  id: '1',
  title: 'JSON:API paints my bikeshed!',
};

const author = {
  id: '2',
  type: 'people',
  firstName: 'Dan',
  lastName: 'Gebhardt',
};

// Example getter functions
async function getArticleAuthor(articleId: string): Promise<ResourceIdentifier> {
  return { id: author.id, type: author.type };
}

async function getAuthorsById(relatedIds: string[]): Promise<Item[]> {
  return [author];
}

async function getAuthorArticles(authorId: string): Promise<ResourceIdentifier[]> {
  return [{ id: article.id, type: article.type }];
}

async function getArticlesById(relatedIds: string[]): Promise<Item[]> {
  return [article];
}

// This configuration defines the relationships between data objects, enabling the serializer to construct JSON:API-compliant relationships in the response.
const config: Config = {
  // This config will apply to data objects with a type of 'articles'.
  articles: {
    // This is the name of the relationship.
    author: {
      type: 'people',
      related: (articleId: string) => getArticleAuthor(articleId),
      include: (relatedIds: string[]) => getAuthorsById(relatedIds),
    },
  },
  authors: {
    articles: {
      type: 'articles',
      related: (authorId: string) => getAuthorArticles(authorId),
      include: (relatedIds: string[]) => getArticlesById(relatedIds),
    },
  },
};

const builder = new Builder(config).create();

const document = await builder.with.data(article).build();

console.log(JSON.stringify(document, null, 2));

This will output:

{
  "jsonapi": {
    "version": "1.1"
  },
  "data": {
    "id": "1",
    "type": "articles",
    "attributes": {
      "title": "JSON:API paints my bikeshed!"
    },
    "relationships": {
      "author": {
        "data": {
          "id": "2",
          "type": "people"
        }
      }
    }
  }
}

Nested Includes

The library supports nested relationships using dot-separated paths in the include query parameter. For example:

const builder = new Builder(config).create({ include: ['comments.author'] });

This will include the comments of the resource, and the author on those comments.

Error Handling

You can add errors to the response using the with.error method:

const document = await builder.with.error(new Error('Something went wrong')).build();

Note: This will remove any data an meta that was previously added to the builder.

Metadata and Links

Add metadata and links to the response:

const document = await builder
  .with.data(articles)
  .with.meta({ rows: 100, totalRows: 5000, page: 4 })
  .with.links({ related: 'http://example.com/articles', next: 'http://example.com/articles?continuation=eyJjdX...' })
  .build()

Important: When the originalUrl parameter is supplied to the Builder.create function, the self link in the generated JSON:API document will consistently reflect this value.

Using with Express.js

Basic Example

import express from 'express';
import { middleware, serialiser, deserialiser, errors } from '@edward644/jsonapi/express';
import { Config, Item } from '@edward644/jsonapi/@types';

const { jsonapi, queries, errorHandler } = middleware;

type Article = {
  title: string;
  createdAt: string;
  updatedAt: string;
  authorId: string;
} & Item;

const config: Config = {
  /* JSON:API configuration */
};

//#region Express API
const app = express();
app.use(jsonapi(config));

app.post('/', queries([]), async (req, res, next) => {
  try {
    const document = deserialiser(req);
    const upsert = document.data<Article>();

    if (Array.isArray(upsert)) throw new errors.ValidationError('Expected a single object, but received an array.');

    const related = document.related();
    upsert.authorId = related.author.at(0);

    const article = await createArticle(upsert);

    const body = await serialiser(req).with.data(article).build();

    res.status(201).send(body);
  } catch (err) {
    next(err);
  }
});

app.get('/', queries(['fields', 'include', 'sort']), async (req, res, next) => {
  try {
    const articles = await getArticles();

    const document = await serialiser(req).with.data(articles).build();

    res.send(document);
  } catch (err) {
    next(err);
  }
});

app.use(errorHandler());

app.listen(9000, () => {
  console.info('API started on port 9000');
});
//#endregion

//#region Example Data
const articles = [
  {
    id: '1',
    type: 'articles',
    title: 'Exploring the Cosmos: A Journey Through Space',
    createdAt: '2025-07-15T10:00:00Z',
    updatedAt: '2025-07-15T10:00:00Z',
  },
  {
    id: '2',
    type: 'articles',
    title: 'The Art of Minimalism: Living with Less',
    createdAt: '2025-08-20T14:30:00Z',
    updatedAt: '2025-08-20T14:30:00Z',
  },
  {
    id: '3',
    type: 'articles',
    title: 'Mastering TypeScript: Tips and Tricks for Developers',
    createdAt: '2025-09-10T09:15:00Z',
    updatedAt: '2025-09-10T09:15:00Z',
  },
  {
    id: '4',
    type: 'articles',
    title: 'The Future of AI: Opportunities and Challenges',
    createdAt: '2025-10-05T16:45:00Z',
    updatedAt: '2025-10-05T16:45:00Z',
  },
  {
    id: '5',
    type: 'articles',
    title: 'Sustainable Living: Eco-Friendly Practices for Everyday Life',
    createdAt: '2025-11-25T08:00:00Z',
    updatedAt: '2025-11-25T08:00:00Z',
  },
];

async function getArticles() {
  return articles;
}

async function createArticle(upsert: Partial<Article>) {
  const errs: Error[] = [];

  if (!upsert.type) errs.push(new errors.ValidationError('Missing property: type'));
  if (!upsert.title) errs.push(new errors.ValidationError('Missing property: title'));
  if (!upsert.authorId) errs.push(new errors.ValidationError('Missing property: authorId'));

  if (errs.length) throw new errors.ErrorCollection(errs);

  const article: Article = {
    id: upsert.id ?? (articles.at(-1)!.id + 1).toString(),
    type: 'articles',
    title: upsert.title!,
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
    authorId: '',
  };

  articles.push(article);

  return article;
}
//#endregion

License

This project is licensed under the ISC License.

Next Steps

  • Filter query improvements.