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

@unchainedshop/api

v4.6.2

Published

GraphQL API layer for the Unchained Engine with Express/Fastify adapters and MCP server

Readme

npm version License: EUPL-1.2

@unchainedshop/api

GraphQL API layer for the Unchained Engine. Provides a complete GraphQL API using Yoga with support for Express and Fastify servers, plus MCP (Model Context Protocol) server for AI integrations.

Installation

npm install @unchainedshop/api

Usage

import express from 'express';
import { startAPIServer } from '@unchainedshop/api';
import { connect } from '@unchainedshop/api/express';

const app = express();

// Start API server (returns GraphQL Yoga server instance)
const graphqlHandler = await startAPIServer({
  unchainedAPI: unchainedCore,
  adminUiConfig: {
    // Admin UI configuration
  },
});

// Connect Express app with Unchained API
connect(app, { graphqlHandler, db, unchainedAPI: unchainedCore }, {
  adminUI: true, // Enable admin UI
});

app.listen(4010);

With Fastify

import Fastify from 'fastify';
import { connect } from '@unchainedshop/api/fastify';

const fastify = Fastify();
await connect(fastify, { graphqlHandler, db, unchainedAPI: unchainedCore });
await fastify.listen({ port: 4010 });

API Overview

Server Setup

| Export | Description | |--------|-------------| | startAPIServer | Create GraphQL server with full schema | | createContextResolver | Create request context resolver | | getCurrentContextResolver | Get current context resolver | | setCurrentContextResolver | Set custom context resolver |

Server Adapters

| Import Path | Export | Description | |-------------|--------|-------------| | @unchainedshop/api/express | connect | Connect Express app with Unchained API | | @unchainedshop/api/express | adminUIRouter | Admin UI Express router | | @unchainedshop/api/fastify | connect | Connect Fastify app with Unchained API |

Context

| Export | Description | |--------|-------------| | UnchainedContext | GraphQL context type | | LocaleContext | Locale-aware context type |

Loaders

Data loaders for efficient batched queries:

| Loader | Description | |--------|-------------| | productLoader | Batch product loading | | assortmentLoader | Batch assortment loading | | userLoader | Batch user loading |

Access Control

| Export | Description | |--------|-------------| | acl | Access control list utilities | | roles | Role definitions and actions | | actions | Available permission actions |

Error Handling

| Export | Description | |--------|-------------| | UnauthorizedError | Authentication error | | PermissionDeniedError | Authorization error | | InvalidIdError | Invalid ID format error | | NotFoundError | Resource not found error |

Events

| Event | Description | |-------|-------------| | API_REQUEST | Emitted on API requests |

Configuration

const server = await startAPIServer({
  unchainedAPI: core,
  roles: customRoles,
  adminUiConfig: {
    basePath: '/admin',
  },
  context: (defaultResolver) => async (props, req, res) => {
    const context = await defaultResolver(props, req, res);
    return {
      ...context,
      // Add custom context
    };
  },
  typeDefs: [
    // Additional GraphQL type definitions
  ],
  resolvers: [
    // Additional resolvers
  ],
});

GraphQL Schema

The API exposes a complete GraphQL schema with:

  • Queries: Products, orders, users, assortments, filters, etc.
  • Mutations: CRUD operations, checkout, authentication
  • Subscriptions: Real-time updates (where supported)

MCP Server

Model Context Protocol server for AI agent integrations:

import { createMCPServer } from '@unchainedshop/api/mcp';

const mcpServer = createMCPServer(unchainedCore);

Security

The API layer implements comprehensive security controls.

Access Control

  • 128+ permission actions covering all API operations
  • Role-Based Access Control (RBAC) with built-in and custom roles
  • ACL enforcement on all GraphQL mutations
  • Ownership validation ensuring users can only access their resources

Session Security

| Variable | Purpose | Default | |----------|---------|---------| | UNCHAINED_TOKEN_SECRET | Session encryption (min 32 chars) | Required | | UNCHAINED_COOKIE_NAME | Cookie name | unchained_token | | UNCHAINED_COOKIE_SAMESITE | SameSite attribute | none | | UNCHAINED_COOKIE_INSECURE | Disable secure flag | false |

Cookies are httpOnly and secure by default.

Error Handling

Errors are designed to prevent information leakage:

  • Generic authentication error messages
  • No distinction between "invalid" vs "expired" tokens
  • Permission errors don't reveal action details

CORS Configuration

CORS behavior depends on NODE_ENV:

| Environment | Default Behavior | |-------------|------------------| | development | Permissive CORS (reflects any origin) + trust proxy headers | | production | No CORS headers (reverse proxy should handle it) |

Environment Variable

| Variable | Purpose | Default | |----------|---------|---------| | UNCHAINED_CORS_ORIGINS | Allowed CORS origins | Auto (see above) |

Programmatic Configuration

// Auto behavior (recommended) - permissive in dev, none in prod
connect(app, { graphqlHandler, db, unchainedAPI });

// Explicit whitelist (production)
connect(app, { graphqlHandler, db, unchainedAPI }, {
  corsOrigins: "https://shop.example.com,https://admin.example.com",
});

// Force permissive (not recommended in production)
connect(app, { graphqlHandler, db, unchainedAPI }, {
  corsOrigins: true,
});

// Disable CORS entirely
connect(app, { graphqlHandler, db, unchainedAPI }, {
  corsOrigins: false,
});

Deployment Scenarios

Scenario 1: Direct TLS (No Reverse Proxy)

For simple deployments where the Node.js server handles TLS directly:

import express from 'express';
import https from 'https';
import fs from 'fs';
import { connect } from '@unchainedshop/api/express';

const app = express();

// Configure CORS for your frontend origins
connect(app, { graphqlHandler, db, unchainedAPI }, {
  corsOrigins: "https://shop.example.com,https://admin.example.com",
});

// Create HTTPS server with your certificates
https.createServer({
  key: fs.readFileSync('/path/to/privkey.pem'),
  cert: fs.readFileSync('/path/to/fullchain.pem'),
}, app).listen(443);

Environment:

NODE_ENV=production
UNCHAINED_CORS_ORIGINS=https://shop.example.com,https://admin.example.com
UNCHAINED_TOKEN_SECRET=your-32-char-secret-here

Scenario 2: Behind Reverse Proxy (Recommended)

For production deployments behind nginx, Caddy, or a cloud load balancer:

import express from 'express';
import { connect } from '@unchainedshop/api/express';

const app = express();

// Trust the reverse proxy for client IP headers
app.set('trust proxy', 1);

// Let the proxy handle CORS, or configure here
connect(app, { graphqlHandler, db, unchainedAPI }, {
  corsOrigins: "https://shop.example.com,https://admin.example.com",
});

app.listen(4010); // Internal port, not exposed

Nginx configuration:

server {
  listen 443 ssl http2;
  server_name api.example.com;

  ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

  location / {
    # Pass client IP to the app
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $host;

    proxy_pass http://localhost:4010;
  }
}

Caddy configuration:

api.example.com {
  reverse_proxy localhost:4010
}

Scenario 3: Proxy Handles CORS

For complex multi-origin setups, let the reverse proxy manage CORS:

// Don't set corsOrigins - let proxy handle it
app.set('trust proxy', 1);
connect(app, { graphqlHandler, db, unchainedAPI });

Nginx with CORS:

location / {
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;

  # CORS whitelist
  set $cors "";
  if ($http_origin ~* "^https://(shop|admin)\.example\.com$") {
    set $cors $http_origin;
  }
  add_header 'Access-Control-Allow-Origin' $cors always;
  add_header 'Access-Control-Allow-Credentials' 'true' always;
  add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
  add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;

  if ($request_method = 'OPTIONS') { return 204; }

  proxy_pass http://localhost:4010;
}

Trust Proxy

In development mode (NODE_ENV=development), trust proxy is automatically enabled.

In production, configure it on your Express/Fastify app before calling connect():

// Express
app.set('trust proxy', 1);  // Trust first hop
app.set('trust proxy', 'loopback');  // Trust loopback addresses
app.set('trust proxy', '10.0.0.0/8');  // Trust specific CIDR

// Fastify
const fastify = Fastify({ trustProxy: true });

SECURITY: Only enable trust proxy if your reverse proxy:

  1. Strips incoming X-Real-IP and X-Forwarded-For headers from clients
  2. Sets X-Real-IP to the actual client IP
  3. Is the only way to reach your API server

See SECURITY.md for complete security documentation including FIPS 140-3 mode.

License

EUPL-1.2