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

kojo

v9.0.2

Published

An event-driven microservice framework. Kōjō (工場) means 'plant' in Japanese.

Downloads

167

Readme

🏭 Kojo

An event-driven microservice framework. Kōjō (工場) means 'factory' in Japanese.

Kojo is straightforward: it has subscribers (event handlers, routes, or endpoints) and functions (reusable business logic). Subscribers subscribe to pub/sub, request/response, or scheduled events from your chosen transport, and functions perform the business logic.

Note: If you're upgrading from v8.x, see the migration guide. TL;DR: services/functions/, serviceDirfunctionsDir

Tests status Coverage Status Known Vulnerabilities

Installation

 npm i kojo

What's New in v9.0.0

  • 🎯 Root-level functions: No need to create directories for simple functions (functions/generateId.js)
  • 🔧 Flexible naming: Use functions/, ops/, or any name that fits your domain
  • ⚠️ Breaking change: Default directory renamed services/functions/
  • Full migration guide →

Usage

NOTE: This package uses native ESM modules (since v8.0.0).

Grouped functions (directory-based)

Create a function group with methods (functions/user/create.js):

export default async function (userData) {

    const [ kojo, logger ] = this;  // kojo instance and logger
    const { pg: pool } = kojo.state;  // get previously set pg connection

    logger.debug('creating', userData);  // logger automatically adds function and method name
    const query = `INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *`;
    const result = await pool.query(query, [userData.name, userData.email]);
    const newRecord = result ? result.rows[0] : null;

    if (newRecord)
        logger.info('created', newRecord);

    return newRecord;
}

Access: kojo.functions.user.create({ name: 'Alice', email: '[email protected]' })

Standalone functions (root-level)

For simple utilities, place them directly in the functions directory (functions/generateId.js):

export default async function () {
    const [ kojo, logger ] = this;
    logger.debug('generating unique ID');
    return crypto.randomUUID();
}

Access: kojo.functions.generateId()

Create a subscriber (subscribers/user.create.js):

export default async (kojo, logger) => {

    const { user } = kojo.functions;  // we defined `user` function group above
    const { nats } = kojo.state;  // get nats connection from state

    nats.subscribe('user.create', async (userData) => {

        logger.debug('received user.create event', userData);
        const newUser = await user.create(userData);

        if (newUser) {
            logger.info('user created, publishing event');
            nats.publish('user.created', newUser);
        }
    });
}

Initialize Kojo and add connections:

import Kojo from 'kojo';
import pg from 'pg';
import NATS from 'nats';

async function main() {

    const kojo = new Kojo({
        name: 'users',
        icon: '👥'
    });

    // Set up connections
    const pool = new pg.Pool({
       user: 'pg_user',
       database: 'db_name',
       password: 'password',
       host: 'localhost'
    });
    kojo.set('pg', pool);  // accessible via kojo.get('pg')

    const nats = await NATS.connect({ servers: 'nats://localhost:4222' });
    kojo.set('nats', nats);

    // Initialize - loads all functions and subscribers
    await kojo.ready();

    console.log('Kojo ready! 🏭');
}

main().catch(console.error);

Functions

Kojo supports two ways to organize functions:

1. Grouped functions (recommended for related logic)

A function group is a directory with files representing methods:

🗀 my-app/
├── 🗀 functions/
│   ├── 🗀 user/              ← Function group
│   │   ├── 🖹 register.js
│   │   ├── 🖹 update.js
│   │   ├── 🖹 list.js
│   │   └── 🖹 test.js        ← Ignored (reserved for unit tests)
│   ├── 🗀 profile/           ← Another function group
│   │   ├── 🖹 create.js
│   │   └── 🖹 update.js
│   └── 🖹 generateId.js      ← Root-level function (NEW in v9!)

These are available via:

  • kojo.functions.user.list()
  • kojo.functions.profile.update()
  • kojo.functions.generateId()

2. Root-level functions (NEW in v9.0.0)

For simple utilities, place files directly in functions/:

// functions/hashPassword.js
export default async function (password) {
    const [ kojo, logger ] = this;
    logger.debug('hashing password');
    return bcrypt.hash(password, 10);
}

Access: kojo.functions.hashPassword('secret123')

Context injection

All functions receive kojo instance and logger via context:

export default async function (userData) {

    const [ kojo, logger ] = this;  // context injection
    const { profile } = kojo.functions;  // access other functions

    logger.debug('creating profile', userData);
    return profile.create(userData);
}

⚠️ Important: Functions must use function() {} syntax, NOT arrow functions () => {}, to receive context.

Internal events

Kojo extends EventEmitter, allowing internal pub/sub:

// In a function - emit an event
export default async function (userData) {
    const [ kojo, logger ] = this;
    const newProfile = await createProfile(userData);
    kojo.emit('profile.created', newProfile);
    return newProfile;
}
// In a subscriber - listen to internal events
export default async (kojo, logger) => {
    kojo.on('profile.created', (newProfile) => {
        logger.info('Profile created internally', newProfile.id);
        // Send notification, update cache, etc.
    });
};

Note: Files named test.js are automatically ignored (reserved for unit tests).

Subscribers

🗀 my-app/
├── 🗀 subscribers/
│   ├── 🖹 user.register.js      ← External event handler
│   ├── 🖹 user.update.js        ← External event handler
│   ├── 🖹 internal.user.created.js  ← Internal event handler
│   └── 🖹 http.get.users.js     ← HTTP route handler

A subscriber exports an async function called once during initialization. It sets up event listeners, HTTP routes, or scheduled tasks. Name files to reflect what they handle.

Example - Internal event subscriber (subscribers/internal.user.registered.js):

export default async (kojo, logger) => {

    const { user } = kojo.functions;
    const nats = kojo.get('nats');

    kojo.on('user.registered', (newUser) => {
        logger.info('user registered, sending notification', newUser.id);
        nats.publish('notification.send', {
            userId: newUser.id,
            type: 'welcome'
        });
    });
}

Example - HTTP route subscriber (subscribers/http.get.users.js):

export default async (kojo, logger) => {

    const { user } = kojo.functions;
    const app = kojo.get('express');

    app.get('/users', async (req, res) => {
        logger.debug('GET /users');
        const users = await user.list();
        res.json(users);
    });
}

Note: Unlike functions, subscribers can use arrow functions and receive kojo/logger as arguments, not context.

Logger

Kojo provides automatic context-aware logging. When logging from user.register, entries automatically include the function and method name:

// In functions/user/register.js
logger.debug('registering user', userData);

Output:

👥 users.Xk9pL DEBUG [user.register] registering user {...user data}

The logger automatically adds:

  • Instance name and ID (users.Xk9pL)
  • Function and method name ([user.register])
  • Support for additional context via logger.setCustomTag('request-id-123')

You can use your own logger by setting it as state (kojo.set('logger', customLogger)), but you'll lose the automatic context tagging.

Docs

Read the [docs].

Configuration

new Kojo({
    subsDir: 'subscribers',      // Subscribers directory (default)
    functionsDir: 'functions',   // Functions directory (default)
    name: '工場',                // Instance name (default: factory)
    icon: '☢',                   // Display icon (default)
    logLevel: 'debug',           // Log level: debug, info, warn, error, silent
    loggerIdSuffix: false,       // Append instance ID to logs (default: false)
    parentPackage: null          // Parent package.json for version display
})

Flexible naming

The directory name determines the API property name:

// Default
new Kojo({ functionsDir: 'functions' })
→ kojo.functions.*

// Domain-specific naming
new Kojo({ functionsDir: 'ops' })
→ kojo.ops.*

new Kojo({ functionsDir: 'handlers' })
→ kojo.handlers.*

Logic placement strategy

Rule of thumb: Place logic in subscribers when in doubt. Move to functions when:

  • Code starts repeating across subscribers
  • Logic becomes complex and needs to be DRY
  • Functionality needs to be reusable

Subscribers are entry points - they make it obvious what events your microservice handles. Functions contain the reusable business logic. By examining the subscribers directory, you should immediately understand what the microservice does.

Test

npm test

Troubleshooting

JSON module import error

If you see:

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".json"

Launch your service with:

node service.js --experimental-json-modules

Or update to Node.js 18+ which handles JSON imports better.

Deprecated warnings

If you see:

Warning: "serviceDir" is deprecated. Please use "functionsDir" instead.

Update your config:

// Old
new Kojo({ serviceDir: 'services' })

// New
new Kojo({ functionsDir: 'services' })

Links