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

@kn0mic/reshaper

v1.0.2

Published

Laravel-inspired API Resource transformations for Node.js. Shape your data with elegance.

Downloads

184

Readme

Reshaper

Laravel-inspired API Resource transformations for Node.js. Shape your data with elegance.

npm version License: MIT

Why Reshaper?

If you've used Laravel's API Resources, you know how elegant they make data transformation. Reshaper brings that same pattern to Node.js:

  • Clean syntax - Transform your data with readable, declarative code
  • Conditional fields - Include fields only when needed
  • Relation handling - Gracefully handle eager-loaded relationships
  • Pagination support - Built-in pagination formatting
  • Zero dependencies - Lightweight and fast
  • TypeScript support - Full type definitions included

Installation

npm install @kn0mic/reshaper

Quick Start

import { Resource } from '@kn0mic/reshaper';

// Define a resource
class UserResource extends Resource {
    static transform(user, options = {}) {
        return Resource.clean({
            id: user.id,
            name: user.name,
            email: Resource.when(options.isAdmin, user.email),
            posts: Resource.whenLoaded(user, 'posts', () =>
                PostResource.collection(user.posts)
            )
        });
    }
}

// Use it
const user = await User.findById(1);
const data = UserResource.transform(user, { isAdmin: true });
// { id: 1, name: 'John', email: '[email protected]' }

Core Concepts

Transform Single Resource

class ProductResource extends Resource {
    static transform(product) {
        return {
            id: product.id,
            name: product.name,
            price: product.price,
            formatted_price: `$${product.price.toFixed(2)}`
        };
    }
}

ProductResource.transform(product);

Transform Collections

const products = await Product.findAll();
ProductResource.collection(products);
// [{ id: 1, name: '...' }, { id: 2, name: '...' }]

Null-Safe Transformation

ProductResource.make(null);  // returns null
ProductResource.make(product);  // returns transformed product

Conditional Fields

when(condition, value)

Include a field only when condition is true:

static transform(user, { isAdmin = false } = {}) {
    return Resource.clean({
        id: user.id,
        name: user.name,
        email: Resource.when(isAdmin, user.email),
        ssn: Resource.when(isAdmin, () => decrypt(user.ssn)) // lazy evaluation
    });
}

whenOrElse(condition, value, default)

Include different values based on condition:

return {
    profile: Resource.whenOrElse(
        isPremium,
        user.fullProfile,
        user.basicProfile
    )
};

whenLoaded(resource, relation, transformer)

Include relation only if it's been loaded (not undefined):

static transform(post) {
    return Resource.clean({
        id: post.id,
        title: post.title,
        // Only included if author was eager-loaded
        author: Resource.whenLoaded(post, 'author', () =>
            UserResource.transform(post.author)
        ),
        // Returns raw value if no transformer provided
        category: Resource.whenLoaded(post, 'category')
    });
}

whenNotNull(value, transformer)

Include value only if it's not null/undefined:

return Resource.clean({
    avatar: Resource.whenNotNull(user.avatar, (url) =>
        `https://cdn.example.com/${url}`
    )
});

Utility Methods

clean(object)

Remove undefined values from object:

Resource.clean({
    id: 1,
    name: 'John',
    email: undefined  // removed
});
// { id: 1, name: 'John' }

merge(...objects)

Merge multiple objects and clean undefined values:

return Resource.merge(
    { id: user.id, name: user.name },
    Resource.when(isAdmin, { email: user.email }),
    Resource.whenLoaded(user, 'profile', (p) => ({ bio: p.bio }))
);

pick(object, fields)

Pick specific fields:

Resource.pick(user, ['id', 'name', 'email']);
// { id: 1, name: 'John', email: '[email protected]' }

omit(object, fields)

Omit specific fields:

Resource.omit(user, ['password', 'secret']);
// User without password and secret

rename(object, keyMap)

Rename keys:

Resource.rename(user, {
    created_at: 'createdAt',
    user_id: 'id'
});

Response Formatting

wrap(data, meta)

Wrap in standard envelope:

UserResource.wrap(UserResource.transform(user));
// { data: { id: 1, name: 'John' } }

UserResource.wrap(users, { version: '1.0' });
// { data: [...], meta: { version: '1.0' } }

paginate(result, options)

Format paginated results:

// With Objection.js
const users = await User.query().page(0, 10);

UserResource.paginate(users, { page: 1, perPage: 10 });
// {
//   data: [...transformed users],
//   meta: {
//     current_page: 1,
//     per_page: 10,
//     total: 100,
//     last_page: 10
//   }
// }

Quick Resources with define()

For simple cases, create resources without extending:

const SimpleUser = Resource.define((user) => ({
    id: user.id,
    name: user.name
}));

SimpleUser.transform(user);
SimpleUser.collection(users);
SimpleUser.paginate(result);

Express.js Integration

// resources/UserResource.js
import { Resource } from '@kn0mic/reshaper';

export default class UserResource extends Resource {
    static transform(user, options = {}) {
        return Resource.clean({
            id: user.id,
            name: `${user.first_name} ${user.last_name}`,
            email: Resource.when(options.includeEmail, user.email),
            posts: Resource.whenLoaded(user, 'posts', () =>
                PostResource.collection(user.posts)
            )
        });
    }
}

// controllers/userController.js
import UserResource from '../resources/UserResource.js';

export const getUsers = async (req, res) => {
    const users = await User.query()
        .withGraphFetched('posts')
        .page(req.query.page - 1, 15);

    res.json(UserResource.paginate(users, {
        page: req.query.page,
        perPage: 15,
        includeEmail: req.user.isAdmin
    }));
};

export const getUser = async (req, res) => {
    const user = await User.query()
        .findById(req.params.id)
        .withGraphFetched('posts');

    res.json(UserResource.wrap(
        UserResource.transform(user)
    ));
};

TypeScript Usage

import { Resource } from '@kn0mic/reshaper';

interface User {
    id: number;
    name: string;
    email: string;
    posts?: Post[];
}

interface UserDTO {
    id: number;
    name: string;
    email?: string;
    posts?: PostDTO[];
}

class UserResource extends Resource {
    static transform(user: User, options: { isAdmin?: boolean } = {}): UserDTO {
        return Resource.clean({
            id: user.id,
            name: user.name,
            email: Resource.when(options.isAdmin, user.email),
            posts: Resource.whenLoaded(user, 'posts', () =>
                PostResource.collection(user.posts!)
            )
        });
    }
}

CommonJS Support

const { Resource } = require('@kn0mic/reshaper');
// or
const Resource = require('@kn0mic/reshaper');

License

MIT