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

nest-hex

v0.4.3

Published

A tiny NestJS-native library for building pluggable adapters (Ports & Adapters / Hexagonal) using class-based Dynamic Modules, with great DX and strong type safety.

Readme

nest-hex

test coverage npm version install size minizipped size License

A tiny, class-based, NestJS-native library for building pluggable adapters following the Ports & Adapters (Hexagonal Architecture) pattern with minimal boilerplate.

Table of Contents

What is nest-hex?

nest-hex eliminates boilerplate when building NestJS applications with the Ports & Adapters (Hexagonal Architecture) pattern. It provides decorators and base classes that handle all the repetitive wiring, letting you focus on business logic.

Why Hexagonal Architecture?

  • 🧪 Testable - Mock infrastructure easily, test business logic in isolation
  • 🔌 Swappable - Switch from S3 to Azure Blob Storage without touching domain code
  • 🎯 Clean - Keep business logic free of infrastructure concerns
  • 🌍 Flexible - Use different adapters for dev, test, and production

Features

  • 🎯 Declarative - Declare port tokens and implementations once using @Adapter({ portToken, implementation })
  • 🏗️ Class-based - Standard NestJS dynamic modules, no function factories
  • 🔒 Type-safe - Compile-time proof that adapters provide the correct port tokens
  • Zero runtime overhead - Uses TypeScript decorators and metadata
  • 📦 Tiny - Core library under 1KB minified
  • 🛠️ Powerful CLI - Generate ports, adapters, and services instantly

Installation

npm install nest-hex
# or
yarn add nest-hex
# or
pnpm add nest-hex
# or
bun add nest-hex

Quick Start

1. Define a Port (Domain Interface)

// storage.port.ts
export const STORAGE_PORT = Symbol('STORAGE_PORT')

export interface StoragePort {
  upload(key: string, data: Buffer): Promise<string>
  download(key: string): Promise<Buffer>
}

2. Create an Adapter (Infrastructure Implementation)

// s3.types.ts - Configuration types
import type { AdapterConfig } from 'nest-hex'
import type { STORAGE_PORT, StoragePort } from './storage.port'

export interface S3ConfigOptions {
  bucket: string
  region: string
}

export type StorageToken = typeof STORAGE_PORT

export type S3AdapterConfig = AdapterConfig<StorageToken, StoragePort>

// s3.service.ts - Implementation service
import { Injectable } from '@nestjs/common'
import type { StoragePort } from './storage.port'
import type { S3ConfigOptions } from './s3.types'

@Injectable()
export class S3Service implements StoragePort {
  constructor(private options: S3ConfigOptions) {}

  async upload(key: string, data: Buffer): Promise<string> {
    // AWS S3 upload logic here
    return `https://s3.amazonaws.com/${this.options.bucket}/${key}`
  }

  async download(key: string): Promise<Buffer> {
    // AWS S3 download logic here
    return Buffer.from('file contents')
  }
}

// s3.adapter.ts - Adapter module
import { Adapter, AdapterBase } from 'nest-hex'
import { STORAGE_PORT } from './storage.port'
import { S3Service } from './s3.service'
import type { S3AdapterConfig, S3ConfigOptions } from './s3.types'

// Single decorator with type safety!
@Adapter<S3AdapterConfig>({
  portToken: STORAGE_PORT,
  implementation: S3Service
})
export class S3Adapter extends AdapterBase<S3ConfigOptions> {}

3. Create a Domain Service

// file.service.ts
import { Injectable } from '@nestjs/common'
import { InjectPort } from 'nest-hex'
import { STORAGE_PORT, type StoragePort } from './storage.port'

@Injectable()
export class FileService {
  constructor(
    @InjectPort(STORAGE_PORT)
    private readonly storage: StoragePort
  ) {}

  async uploadUserAvatar(userId: string, image: Buffer): Promise<string> {
    const key = `avatars/${userId}.jpg`
    return this.storage.upload(key, image)
  }
}

4. Create a Domain Module

// file.module.ts
import { Module } from '@nestjs/common'
import { DomainModule } from 'nest-hex'
import { FileService } from './file.service'

@Module({})
export class FileModule extends DomainModule {}

5. Wire It Up

// app.module.ts
import { Module } from '@nestjs/common'
import { FileModule } from './file.module'
import { S3Adapter } from './s3.adapter'

@Module({
  imports: [
    FileModule.register({
      adapter: S3Adapter.register({
        bucket: process.env.S3_BUCKET || 'my-bucket',
        region: process.env.AWS_REGION || 'us-east-1'
      })
    })
  ]
})
export class AppModule {}

That's it! You now have a fully type-safe, pluggable storage adapter. 🎉

CLI

Generate ports, adapters, and services instantly with the built-in CLI:

# Initialize configuration
npx nest-hex init

# Generate a port (domain interface)
npx nest-hex generate port ObjectStorage

# Generate an adapter for the port
npx nest-hex generate adapter S3 --port ObjectStorage

# Or generate both at once
npx nest-hex generate full ObjectStorage S3

See CLI Documentation for complete command reference, configuration options, and template customization.

Key Benefits

Before (Manual Boilerplate)

@Module({})
export class S3StorageModule {
  static register(options: S3Options): DynamicModule {
    return {
      module: S3StorageModule,
      providers: [
        S3StorageService,
        { provide: STORAGE_PORT, useExisting: S3StorageService },
        // More boilerplate...
      ],
      exports: [STORAGE_PORT],
    }
  }
}

After (With nest-hex)

// s3.types.ts
export type StorageToken = typeof STORAGE_PORT
export type S3AdapterConfig = AdapterConfig<StorageToken, StoragePort>

// s3.adapter.ts
@Adapter<S3AdapterConfig>({
  portToken: STORAGE_PORT,
  implementation: S3StorageService
})
export class S3Adapter extends AdapterBase<S3ConfigOptions> {}

Swappable Infrastructure

The real power: swap infrastructure without touching business logic.

// Development: Use local filesystem
const adapter = process.env.NODE_ENV === 'production'
  ? S3Adapter.register({ bucket: 'prod-bucket', region: 'us-east-1' })
  : LocalStorageAdapter.register({ basePath: './uploads' })

@Module({
  imports: [FileModule.register({ adapter })]
})
export class AppModule {}

Your FileService business logic never changes. Only the adapter changes.

Advanced Features

Async Configuration with Dependency Injection

@Module({
  imports: [
    FileModule.register({
      adapter: S3Adapter.registerAsync({
        imports: [ConfigModule],
        inject: [ConfigService],
        useFactory: (config: ConfigService) => ({
          bucket: config.get('S3_BUCKET')!,
          region: config.get('AWS_REGION')!
        })
      })
    })
  ]
})
export class AppModule {}

Adapters with Dependencies

// axios.types.ts
export type HttpClientToken = typeof HTTP_CLIENT_PORT
export type AxiosAdapterConfig = AdapterConfig<HttpClientToken, HttpClientPort>

// axios.adapter.ts
@Adapter<AxiosAdapterConfig>({
  portToken: HTTP_CLIENT_PORT,
  implementation: AxiosHttpClient,
  imports: [HttpModule],
  providers: [
    { provide: 'HTTP_CONFIG', useValue: { timeout: 5000 } }
  ]
})
export class AxiosAdapter extends AdapterBase<AxiosConfigOptions> {}

Mock Adapters for Testing

// mock-storage.types.ts
export type StorageToken = typeof STORAGE_PORT
export type MockStorageAdapterConfig = AdapterConfig<StorageToken, StoragePort>

// mock-storage.service.ts
@Injectable()
class MockStorageService implements StoragePort {
  async upload(key: string, data: Buffer): Promise<string> {
    return `mock://storage/${key}`
  }
  async download(key: string): Promise<Buffer> {
    return Buffer.from('mock data')
  }
}

// mock-storage.adapter.ts
@Adapter<MockStorageAdapterConfig>({
  portToken: STORAGE_PORT,
  implementation: MockStorageService
})
export class MockStorageAdapter extends AdapterBase<{}> {}

// Use in tests
const module = await Test.createTestingModule({
  imports: [
    FileModule.register({
      adapter: MockStorageAdapter.register({})
    })
  ]
}).compile()

Documentation

📚 Complete Documentation:

📖 Quick Links:

Examples

See the examples/ directory for complete working examples:

  • Object Storage - S3 adapter with file upload/download
  • Currency Rates - HTTP API adapter with rate conversion
  • Mock Patterns - Testing with mock adapters

License

MIT © [Your Name]

Contributing

Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and development process.


Built with ❤️ for the NestJS community