npm package: @royzhang107/nest-casing
Description: NestJS module for automatic request/response casing transformation (snake_case ↔ camelCase)
Author: Roy Zhang
Latest version: 0.1.8

# @royzhang107/nest-casing

A NestJS module for automatic request/response casing transformation between `snake_case` and `camelCase`.

Write your DTOs in camelCase while your API communicates in snake_case — automatically.

## Features

- 🔄 **Bidirectional transformation** — Incoming `snake_case` → `camelCase`, outgoing `camelCase` → `snake_case`
- 🌐 **Global module** — Apply once, works everywhere
- 🎯 **Flexible** — Use globally or per-route with interceptors/pipes
- 📦 **Pure TypeScript** — Zero external dependencies for casing logic
- 🔧 **Configurable** — Optional ValidationPipe integration
- ⚡ **Dual ESM/CJS** — Works in any module system

## Installation

```bash
npm install @royzhang107/nest-casing
```

## Quick Start

### Global Module (Recommended)

Import `CasingModule` in your root module:

```typescript
import { Module } from '@nestjs/common';
import { CasingModule } from '@royzhang107/nest-casing';

@Module({
  imports: [
    CasingModule.forRoot(),
  ],
})
export class AppModule {}
```

That's it! Your API now:
- Accepts `snake_case` request bodies and transforms them to `camelCase`
- Returns responses with `camelCase` keys transformed to `snake_case`

### Configuration Options

```typescript
CasingModule.forRoot({
  // Default: 'snake-to-camel'
  // - 'snake-to-camel': Input snake_case → camelCase, Output camelCase → snake_case
  // - 'camel-to-snake': Input camelCase → snake_case, Output snake_case → camelCase
  direction: 'snake-to-camel',

  // Enable built-in ValidationPipe
  enableValidation: true,
  validationOptions: {
    transform: true,
    whitelist: true,
    forbidNonWhitelisted: false,
  },
})
```

### Async Configuration

Use `forRootAsync` when options depend on other providers:

```typescript
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { CasingModule } from '@royzhang107/nest-casing';

@Module({
  imports: [
    ConfigModule.forRoot(),
    CasingModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        direction: config.get('CASING_DIRECTION') || 'snake-to-camel',
        enableValidation: true,
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}
```

## Per-Route Usage

Use interceptors and pipes individually when you need fine-grained control:

```typescript
import { Controller, Post, Body, UseInterceptors, UsePipes } from '@nestjs/common';
import {
  CamelToSnakeInterceptor,
  SnakeToCamelPipe,
} from '@royzhang107/nest-casing';

@Controller('users')
export class UsersController {
  @Post()
  @UseInterceptors(CamelToSnakeInterceptor)
  @UsePipes(SnakeToCamelPipe)
  create(@Body() createUserDto: CreateUserDto) {
    // createUserDto has camelCase keys
    return this.usersService.create(createUserDto);
    // Response will have snake_case keys
  }
}
```

## Utility Functions

The casing utilities are also exported for direct use:

```typescript
import {
  toCamelCase,
  toSnakeCase,
  keysToCamel,
  keysToSnake,
} from '@royzhang107/nest-casing';

// String transformation
toCamelCase('user_name');        // 'userName'
toSnakeCase('userName');         // 'user_name'

// Deep object key transformation
keysToCamel({ user_name: 'John', user_age: 30 });
// { userName: 'John', userAge: 30 }

keysToSnake({ userName: 'John', userAge: 30 });
// { user_name: 'John', user_age: 30 }
```

### Available Utilities

| Function | Description |
|----------|-------------|
| `toCamelCase(str)` | Convert string to camelCase |
| `toSnakeCase(str)` | Convert string to snake_case |
| `toPascalCase(str)` | Convert string to PascalCase |
| `toKebabCase(str)` | Convert string to kebab-case |
| `keysToCamel(obj)` | Deep transform object keys to camelCase |
| `keysToSnake(obj)` | Deep transform object keys to snake_case |
| `keysToPascal(obj)` | Deep transform object keys to PascalCase |
| `keysToKebab(obj)` | Deep transform object keys to kebab-case |
| `transformKeys(obj, fn)` | Deep transform object keys with custom function |

## Usage in Nx Monorepo

### Importing the Library

In your Nx workspace, import from the library path:

```typescript
// In apps/your-app/src/app/app.module.ts
import { CasingModule } from '@royzhang107/nest-casing';
```

### Building the Library

```bash
# Build the library
npx nx build @royzhang/nest-casing

# Build with production config
npx nx build @royzhang/nest-casing --configuration=production

# Run tests
npx nx test @royzhang/nest-casing

# Lint
npx nx lint @royzhang/nest-casing
```

### Path Mapping

The workspace already has the path mapping in `tsconfig.base.json`:

```json
{
  "compilerOptions": {
    "paths": {
      "@royzhang107/nest-casing": ["libs/nestjs-casing/src/index.ts"]
    }
  }
}
```

## API Reference

### CasingModule

| Method | Description |
|--------|-------------|
| `forRoot(options?)` | Register module with static configuration |
| `forRootAsync(options)` | Register module with async configuration |

### Interceptors

| Class | Description |
|-------|-------------|
| `CamelToSnakeInterceptor` | Transform response keys from camelCase to snake_case |
| `SnakeToCamelInterceptor` | Transform response keys from snake_case to camelCase |

### Pipes

| Class | Description |
|-------|-------------|
| `SnakeToCamelPipe` | Transform request body keys from snake_case to camelCase |
| `CamelToSnakePipe` | Transform request body keys from camelCase to snake_case |

## Example Flow

```
Request Body (from client):
{
  "user_name": "john_doe",
  "email_address": "john@example.com"
}
        ↓
SnakeToCamelPipe transforms to:
{
  "userName": "john_doe",
  "emailAddress": "john@example.com"
}
        ↓
Your controller/service works with camelCase
        ↓
CamelToSnakeInterceptor transforms response:
{
  "user_name": "john_doe",
  "email_address": "john@example.com",
  "created_at": "2024-01-01T00:00:00Z"
}
```

## License

MIT