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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@neoma/route-model-binding

v0.2.0

Published

Laravel-inspired route model binding for NestJS - automatically resolve database entities from route parameters

Downloads

23

Readme

@neoma/route-model-binding

Laravel-inspired route model binding for NestJS applications. Automatically resolve database entities from route parameters with zero boilerplate.

Why Route Model Binding?

If you've used Laravel, you know how elegant route model binding makes your controllers. Instead of manually fetching entities in every controller method, they're automatically resolved from route parameters.

This library brings that same developer experience to NestJS:

Before (without this library):

@Get('/users/:userId/posts/:postId')
async getPost(
  @Param('userId') userId: string,
  @Param('postId') postId: string,
) {
  const user = await this.userRepository.findOne({ where: { id: userId } })
  if (!user) throw new NotFoundException('User not found')

  const post = await this.postRepository.findOne({ where: { id: postId } })
  if (!post) throw new NotFoundException('Post not found')

  return { user, post }
}

After (with @neoma/route-model-binding):

@Get('/users/:user/posts/:post')
getPost(
  @RouteModel('user') user: User,
  @RouteModel('post') post: Post,
) {
  return { user, post }  // Entities are automatically resolved!
}

Installation

npm install @neoma/route-model-binding

Peer Dependencies

This library requires the following peer dependencies:

  • @nestjs/common (^11.x)
  • @nestjs/core (^11.x)
  • @nestjs/typeorm (^11.x)
  • typeorm (^0.3.27)

Quick Start

1. Import the RouteModelBindingModule

import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common"
import { TypeOrmModule } from "@nestjs/typeorm"
import { 
  RouteModelBindingModule, 
  RouteModelBindingMiddleware 
} from "@neoma/route-model-binding"

@Module({
  imports: [
    TypeOrmModule.forRoot({
      // your TypeORM configuration
    }),
    RouteModelBindingModule.forRoot(), // Add this line
  ],
  controllers: [AppController],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer): void {
    consumer.apply(RouteModelBindingMiddleware).forRoutes(
      "users/:user",
      "users/:user/posts/:post",
      // Add other routes with model parameters here
    )
  }
}

2. Name your route parameters to match your entity names

The middleware automatically maps route parameters to TypeORM entities. The parameter name must match your entity name (case-sensitive).

// If you have a User entity and a Post entity
@Get('/users/:user/posts/:post')  // :user will fetch User entity, :post will fetch Post entity

3. Use the @RouteModel decorator in your controllers

import { Controller, Get } from "@nestjs/common"
import { RouteModel } from "@neoma/route-model-binding"
import { User } from "./entities/user.entity"
import { Post } from "./entities/post.entity"

@Controller()
export class AppController {
  @Get("/users/:user")
  getUser(@RouteModel("user") user: User) {
    return user // User is automatically fetched from database
  }

  @Get("/users/:user/posts/:post")
  getPost(@RouteModel("user") user: User, @RouteModel("post") post: Post) {
    return { user, post } // Both entities are automatically resolved
  }
}

How It Works

  1. Route Parameter Naming: When you define a route like /users/:user, the :user parameter tells the middleware to look for a User entity.

  2. Automatic Resolution: The middleware intercepts incoming requests, extracts the parameter values (usually UUIDs or IDs), and queries the database using TypeORM.

  3. Entity Injection: Found entities are attached to the request object and made available via the @RouteModel decorator.

  4. Automatic 404: If an entity isn't found, the middleware automatically throws a NotFoundException with a descriptive message.

API Reference

RouteModelBindingModule

The module that provides route model binding functionality.

RouteModelBindingModule.forRoot(config?: RouteModelBindingConfig)

Parameters:

  • config (optional) - Configuration object for customizing resolution behavior

Example:

import { RouteModelBindingModule } from "@neoma/route-model-binding"

@Module({
  imports: [
    RouteModelBindingModule.forRoot({
      defaultResolver: ({ id }) => ({ id, deletedAt: null }),
    }),
  ],
})
export class AppModule {}

RouteModelBindingMiddleware

The middleware that handles the automatic resolution of entities. You must specify the routes that contain model parameters.

import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common"
import { RouteModelBindingMiddleware } from "@neoma/route-model-binding"

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer): void {
    consumer.apply(RouteModelBindingMiddleware).forRoutes(
      "users/:user",
      "users/:user/posts/:post",
      "articles/:article",
      // List all routes that have model parameters
    )
  }
}

@RouteModel Decorator

Extracts the resolved entity from the request.

@RouteModel(parameterName: string): ParameterDecorator

Parameters:

  • parameterName - The name of the route parameter (without the colon)

Example:

@Get('/articles/:article')
getArticle(@RouteModel('article') article: Article) {
  return article
}

Advanced Usage

Custom Resolvers

You can customize how entities are resolved by providing a default resolver function. This is useful for implementing features like soft delete filtering, multi-tenancy, or custom query logic.

import { RouteModelBindingModule } from "@neoma/route-model-binding"

@Module({
  imports: [
    RouteModelBindingModule.forRoot({
      // Custom default resolver for all entities
      defaultResolver: ({ id }) => ({
        id,
        deletedAt: null, // Only find non-soft-deleted entities
      }),
    }),
  ],
})
export class AppModule implements NestModule {
  // ... middleware configuration
}

Resolver Function

The resolver function receives a context object and returns a TypeORM where clause:

type ResolverFunction = (context: ResolverContext) => FindOptionsWhere<any> | Promise<FindOptionsWhere<any>>

interface ResolverContext {
  name: string                  // The parameter name (e.g., 'user')
  id: string                    // The route parameter value
  req: Request                  // Express request object (includes req.routeModels)
}

Common Use Cases

Soft Delete Support:

RouteModelBindingModule.forRoot({
  defaultResolver: ({ id }) => ({
    id,
    deletedAt: null, // Exclude soft-deleted entities
  }),
})

Multi-tenancy:

RouteModelBindingModule.forRoot({
  defaultResolver: ({ id, req }) => ({
    id,
    tenantId: req.user?.tenantId, // Scope to current tenant
  }),
})

Complex Authorization:

RouteModelBindingModule.forRoot({
  defaultResolver: async ({ id, req }) => {
    return {
      id,
      // Access previously resolved models via req.routeModels
      ownerId: req.user?.id,
      teamId: req.routeModels?.team?.id,
    }
  },
})

Parameter-Specific Resolvers

You can override the default resolver for specific route parameters using paramResolvers. This is useful when different entities need different resolution logic.

import { RouteModelBindingModule } from "@neoma/route-model-binding"

@Module({
  imports: [
    RouteModelBindingModule.forRoot({
      // Default resolver for all parameters
      defaultResolver: ({ id }) => ({ id, deletedAt: null }),

      // Override resolver for specific parameters
      paramResolvers: {
        // Custom logic for 'user' parameter
        user: ({ id, req }) => ({
          id,
          deletedAt: null,
          isActive: true,
          tenantId: req.user?.tenantId,
        }),

        // Custom logic for 'post' parameter
        post: async ({ id, req }) => ({
          id,
          deletedAt: null,
          // Only show published posts or posts owned by current user
          status: req.user ? undefined : 'published',
          authorId: req.user?.id,
        }),
      },
    }),
  ],
})
export class AppModule {}

Key points:

  • Parameter names in paramResolvers should be lowercase (matching the route parameter)
  • Parameter-specific resolvers take precedence over the defaultResolver
  • Each resolver receives the same ResolverContext with name, id, and req

Use cases:

  • Different soft-delete behavior per entity
  • Entity-specific authorization rules
  • Custom query logic for specific models
  • Mixed public/private entity resolution

Error Handling

When an entity is not found, the middleware throws a NestJS NotFoundException with a message like:

Could not find User with id 123e4567-e89b-12d3-a456-426614174000

You can catch and customize these errors using NestJS exception filters if needed.

TypeORM Entity Requirements

Your entities must be registered with TypeORM and the entity name must match the route parameter name:

@Entity()
export class User {
  @PrimaryGeneratedColumn("uuid")
  id: string

  @Column()
  name: string

  // ... other fields
}

Route parameter :user will map to the User entity class.

Comparison with Laravel

If you're coming from Laravel, here's how this library compares:

| Laravel | @neoma/route-model-binding | | ---------------------------------------- | ------------------------------------ | | Route::get('/users/{user}', ...) | @Get('/users/:user') | | Automatic injection via type-hinting | Use @RouteModel('user') decorator | | Customizable via resolveRouteBinding() | Custom resolvers via forRoot() | | Soft-deleted model handling | ✅ Via custom resolvers | | Custom binding keys | ✅ Via custom resolvers | | Multi-tenancy support | ✅ Via custom resolvers |

Contributing

Contributions are welcome! This library aims to bring a small part of Laravel's developer experience to the NestJS ecosystem.

Development Setup

# Install dependencies
npm install

# Run tests
npm test

# Build the library
npm run build

# Lint the code
npm run lint

Roadmap

  • [x] Support for custom binding queries (via default resolver)
  • [x] Soft-delete support (via custom resolvers)
  • [x] Custom resolution logic (via default resolver)
  • [x] Per-parameter resolvers (entity-specific resolution)
  • [ ] Support for custom binding keys (e.g., resolve by slug instead of id)
  • [ ] Route model binding for non-TypeORM ORMs
  • [ ] Nested relationship loading
  • [ ] Optional bindings (don't throw 404)
  • [ ] Custom error handling per resolver

License

MIT

Credits

Inspired by Laravel's Route Model Binding - bringing the elegance of Laravel to the NestJS ecosystem.

Support

If you find this library helpful, please consider giving it a star on GitHub!

Found a bug or have a feature request? Open an issue