<!-- Markdown mirror of https://justscale.sh/docs/fundamentals/features -->

# Features

Composable, reusable application modules

Features are self-contained modules that bundle services, controllers, and lifecycle hooks into reusable packages. They declare what they need (`.requires()`) and what they provide (`.provides()`), enabling modular composition with compile-time dependency checking.

## What is a Feature?

A feature is created with `createFeatureBuilder()` — a fluent builder that produces a feature token you can `.add()` to your app. Features compose naturally:

logging-feature.tsTypeScript

```typescript
import { createFeatureBuilder } from '@justscale/core';
import { LoggerService } from './logger-service';

// Simple feature — provides a service
export const LoggingFeature = createFeatureBuilder()
  .name('logging')
  .provides((b) => b.add(LoggerService));

// Use in app
import JustScale from '@justscale/core';

const app = JustScale()
  .add(LoggingFeature)
  .build();
```

## Creating a Feature

Use `createFeatureBuilder()` to define a feature. The `.provides()` callback receives a builder where you `.add()` services and controllers:

user-feature.tsTypeScript

```typescript
import { createFeatureBuilder } from '@justscale/core';
import { UserService, AuthService } from './services';
import { UsersController, AuthController } from './controllers';

export const UserFeature = createFeatureBuilder()
  .name('users')
  .provides((b) => b
    .add(UserService)
    .add(AuthService)
    .add(UsersController)
    .add(AuthController)
  );

// Use it
import JustScale from '@justscale/core';

const app = JustScale()
  .add(UserFeature)
  .build();
```

## Feature Dependencies

Features can depend on tokens or other features using `.requires()`. Dependencies must be provided before the feature is added:

feature-dependencies.tsTypeScript

```typescript
import { createFeatureBuilder, bindService } from '@justscale/core';
import { ModelRepository } from '@justscale/core/models';
import { User } from './models';

// Feature that requires a User repository and an email sender
export const AuthFeature = createFeatureBuilder()
  .name('auth')
  .requires(ModelRepository.of(User))
  .requires(AbstractEmailSender)
  .provides((b) => b
    .add(PasswordService)
    .add(UserService)
    .add(SessionService)
    .add(AuthController)
  );

// When adding AuthFeature, its requirements must already be met
import JustScale, { bindRepository } from '@justscale/core';

const app = JustScale()
  .add(PgClient)
  .add(bindRepository(ModelRepository.of(User), UserRepository))
  .add(bindService(AbstractEmailSender, ConsoleEmailSender))
  .add(AuthFeature)  // Requirements satisfied above
  .build();
```

## Lifecycle Hooks

Features can run code when the app starts or stops using `.onStart()` and `.onStop()`:

lifecycle-hooks.tsTypeScript

```typescript
import { createFeatureBuilder } from '@justscale/core';

export const DatabaseFeature = createFeatureBuilder()
  .name('database')
  .onStart(async ({ resolve }) => {
    const client = resolve(PgClient);
    await client.connect();
    console.log('Database connected');
  })
  .onStop(async () => {
    console.log('Database disconnected');
  })
  .provides((b) => b.add(PgClient));
```

## Requiring Other Features

When a feature requires another feature, the required feature's provided tokens become available in the `.provides()` builder:

requiring-features.tsTypeScript

```typescript
import { createFeatureBuilder } from '@justscale/core';

// Base feature
export const DatabaseFeature = createFeatureBuilder()
  .name('database')
  .provides((b) => b.add(PgClient));

// Feature that requires DatabaseFeature
export const UserFeature = createFeatureBuilder()
  .name('users')
  .requires(DatabaseFeature)  // PgClient is now available
  .provides((b) => b
    .add(UserRepository)  // Can depend on PgClient
    .add(UserService)
  );

// In the app, add both
import JustScale from '@justscale/core';

const app = JustScale()
  .add(DatabaseFeature)
  .add(UserFeature)  // Works because DatabaseFeature is already added
  .build();
```

## Composing Features

Build complex applications by layering features. Each feature focuses on one capability:

composing.tsTypeScript

```typescript
import JustScale, { bindService, bindRepository } from '@justscale/core';
import { ModelRepository } from '@justscale/core/models';
import { AuthFeature, AuthEndpointsFeature, User, Session } from '@justscale/auth';

const app = JustScale()
  // Infrastructure
  .add(PgClient)
  .add(PostgresLockFeature)
  .add(InMemoryProcessFeature)

  // Auth (requirements: User repo, Session repo, email sender)
  .add(bindRepository(ModelRepository.of(User), UserRepository))
  .add(bindRepository(ModelRepository.of(Session), SessionRepository))
  .add(bindService(AbstractEmailSender, ConsoleEmailSender))
  .add(AuthFeature)
  .add(AuthEndpointsFeature)

  // Domain
  .add(TicketRepository)
  .add(TicketService)
  .add(TicketController)
  .build();
```

## Service Bindings in Features

Features can use `bindService` and `bindRepository` inside their `.provides()` callback to wire abstract tokens to implementations:

service-bindings.tsTypeScript

```typescript
import { createFeatureBuilder, bindService } from '@justscale/core';

// Abstract service
abstract class AbstractLogger {
  abstract log(message: string): void;
}

// Concrete implementation
class ConsoleLogger extends defineService({
  inject: {},
  factory: () => ({
    log: (message: string) => console.log(message),
  }),
}) {}

// Feature binds abstract to concrete
export const LoggingFeature = createFeatureBuilder()
  .name('logging')
  .provides((b) => b
    .add(ConsoleLogger)
    .add(bindService(AbstractLogger, ConsoleLogger))
  );

// Other services can now inject AbstractLogger
class UserService extends defineService({
  inject: { logger: AbstractLogger },
  factory: ({ logger }) => ({
    create: (user) => {
      logger.log('Creating user');
      return user;
    },
  }),
}) {}
```

## Official Features

JustScale provides official features for common functionality:

- @justscale/auth — Authentication, sessions, 2FA, password reset
- @justscale/feature-otel — OpenTelemetry tracing and metrics
- @justscale/feature-shell — Interactive CLI shell for debugging

official-features.tsTypeScript

```typescript
import JustScale, { bindRepository, bindService } from '@justscale/core';
import { ModelRepository } from '@justscale/core/models';
import {
  AuthFeature, AuthEndpointsFeature,
  User, Session, AbstractEmailSender, ConsoleEmailSender,
} from '@justscale/auth';

const app = JustScale()
  .add(PgClient)
  .add(bindRepository(ModelRepository.of(User), PgUserRepository))
  .add(bindRepository(ModelRepository.of(Session), PgSessionRepository))
  .add(bindService(AbstractEmailSender, ConsoleEmailSender))
  .add(AuthFeature)
  .add(AuthEndpointsFeature)
  .build();
```

## Best Practices

- One feature per domain — group related functionality together
- Explicit dependencies — use .requires() to declare what a feature needs, so TypeScript catches missing dependencies at compile time
- Separate services from endpoints — provide services in one feature and controllers in another (like AuthFeature + AuthEndpointsFeature) so consumers can build their own endpoints
- Use lifecycle hooks — .onStart() for initialization, .onStop() for cleanup
- Keep features focused — a feature should represent one clear capability

## Next Steps

- Services
- Features Overview
- OpenAPI
