npm package: @rhino-ai/orchard9-expresstsx
Description: A zero-configuration, production-ready server framework for TypeScript services
Latest version: 0.3.0

# 🌲 Orchard9 Express TSX

A zero-configuration, production-ready server framework for TypeScript services.

[![npm version](https://img.shields.io/npm/v/@rhino-ai/orchard9-expresstsx.svg)](https://www.npmjs.com/package/@rhino-ai/orchard9-expresstsx)
[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-blue)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Features

- **Zero-config startup** - Just provide routes and go!
- **Batteries included** - Database, Redis, WebSockets, Config, Logging - all ready to use
- **Production ready** - Kubernetes health checks, metrics, and security best practices built-in
- **Fully typed** - Complete TypeScript support with intelligent autocomplete
- **Extensible** - Easily add custom middleware, routes, and services

## Installation

```bash
npm install @rhino-ai/orchard9-expresstsx
```

### Docker Installation

You can also use Docker to run the framework:

```bash
# Clone the repository

# Start development environment
npm run docker:dev

# Or build and start
npm run docker:dev:build
```

## Quick Start

Create an `app.ts` file:

```typescript
import { createApp } from '@rhino-ai/orchard9-expresstsx';
import userRoutes from './routes/users';

// That's it - everything is automatically configured from your config files!
const app = createApp({
  routes: [
    {
      path: '/api/users',
      router: userRoutes
    }
  ]
});

app.start().then(() => {
  app.logger.info('🚀 Application started successfully!');
});
```

Create a route in `routes/users.ts`:

```typescript
import { Router, Request, Response } from '@rhino-ai/orchard9-expresstsx';

const router = Router();

router.get('/', async (req: Request, res: Response) => {
  const users = await req.db('users').select('*');
  return res.json(users);
});

export default router;
```

Create a default configuration in `config/default.yaml`:

```yaml
app:
  name: my-awesome-service
  port: 3000

database:
  host: localhost
  port: 5432
  user: postgres
  password: postgres
  database: my_app

redis:
  host: localhost
  port: 6379
```

Start your application:

```bash
ts-node app.ts
```

## Project Structure

We recommend the following project structure:

```
my-service/
├── config/
│   ├── default.yaml     # Default configuration
│   ├── development.yaml # Development overrides
│   └── production.yaml  # Production overrides
├── src/
│   ├── routes/          # Express route definitions
│   │   └── users.ts
│   ├── services/        # Business logic
│   │   └── user-service.ts
│   ├── websocket/       # WebSocket handlers
│   │   └── chat-handler.ts
│   ├── types/           # TypeScript type definitions
│   │   └── index.ts
│   └── app.ts           # Main application entry point
├── package.json
└── tsconfig.json
```

## Core Concepts

### The App Instance

The app instance provides unified access to all underlying services:

```typescript
const app = createApp();

// Access database directly
const users = await app.db('users').select('*');

// Access Redis
await app.redis.set('key', 'value');

// Access logger
app.logger.info('Something happened');

// Access WebSocket server
app.websocket.broadcast('event', { data: 'message' });

// Access config
const dbConfig = app.config.get('database');
```

### Request Extensions

Every request is automatically extended with core services:

```typescript
router.get('/profile', async (req, res) => {
  // Request-scoped logger with request ID
  req.logger.info('Fetching profile');
  
  // Database access
  const user = await req.db('users').where('id', req.params.id).first();
  
  // Validation helpers
  const { valid, data, errors } = req.validate(user, userSchema);
  
  return res.json(user);
});
```

### WebSockets

WebSocket support is built-in and easy to use:

```typescript
import { createApp } from '@rhino-ai/orchard9-expresstsx';

const app = createApp({
  websocket: {
    handlers: [
      {
        event: 'chat.message',
        handler: (message, socket, metadata) => {
          // Handle the message
          app.websocket.broadcastToRoom('chat', 'chat.message', message.payload);
        }
      }
    ]
  }
});

// Client connection handler
app.websocket.onConnection((socket, req, metadata) => {
  app.logger.info(`Client connected: ${metadata.clientId}`);
  app.websocket.joinRoom('chat', metadata.clientId, socket, metadata);
});
```

### Configuration

Configuration is automatically loaded from the `config/` directory:

```typescript
// Access configuration
const dbConfig = app.config.get('database');
const appName = app.config.get('app.name');

// Watch for configuration changes
app.config.onChange('database', (newValue, oldValue) => {
  app.logger.info('Database configuration changed');
});
```

### Protobuf Support

Protocol Buffers are fully supported:

```typescript
import { createApp } from '@rhino-ai/orchard9-expresstsx';

const app = createApp({
  protobuf: {
    schemaPath: './proto',
    handlers: [
      {
        messageType: 'UserMessage',
        handler: async (data, context) => {
          // Handle the protobuf message
          return { status: 'success' };
        }
      }
    ]
  }
});

// Later, serialize/deserialize messages
const userProto = await app.protobuf.loadSchema('user.proto');
const userType = app.protobuf.getType(userProto, 'User');
const buffer = app.protobuf.serialize(userType, { id: 1, name: 'John' });
```

## API Reference

### `createApp(options?)`

Creates and configures the application.

```typescript
const app = createApp({
  // App configuration
  name: 'my-service',
  port: 3000,
  
  // Route configuration
  routes: [
    { path: '/api/users', router: userRoutes },
    { path: '/api/products', router: productRoutes }
  ],
  
  // Middleware configuration
  middleware: {
    before: [customMiddleware1, customMiddleware2],
    after: [customMiddleware3]
  },
  
  // WebSocket configuration
  websocket: {
    path: '/ws',
    handlers: [{
      event: 'message',
      handler: messageHandler
    }]
  },
  
  // Health check configuration
  health: {
    path: '/health',
    kubernetes: true,
    checks: [customHealthCheck]
  },
  
  // Protobuf configuration
  protobuf: {
    schemaPath: './proto',
    handlers: [{
      messageType: 'UserMessage',
      handler: userMessageHandler
    }]
  }
});
```

### App Methods

- `app.start()` - Starts the application
- `app.stop()` - Gracefully stops the application
- `app.db` - Access the database (Knex instance)
- `app.redis` - Access the Redis client
- `app.logger` - Access the logger
- `app.config` - Access the configuration
- `app.websocket` - Access the WebSocket server
- `app.events` - Access the event bus
- `app.protobuf` - Access the Protocol Buffers utilities
- `app.validation` - Access the validation utilities

## Examples

### Complete Example with All Features

```typescript
import { createApp, Router } from '@rhino-ai/orchard9-expresstsx';
import { z } from 'zod';

// Define routes
const userRouter = Router();

userRouter.get('/', async (req, res) => {
  const users = await req.db('users').select('*');
  return res.json(users);
});

userRouter.post('/', async (req, res) => {
  // Validation schema
  const userSchema = z.object({
    name: z.string().min(2),
    email: z.string().email()
  });
  
  // Validate request body
  const { valid, data, errors } = req.validate(req.body, userSchema);
  
  if (!valid) {
    return res.status(400).json({ errors });
  }
  
  // Insert into database with transaction
  const userId = await req.transaction(async (trx) => {
    const [id] = await trx('users').insert(data).returning('id');
    await trx('user_logs').insert({ user_id: id, action: 'created' });
    return id;
  });
  
  // Publish event
  req.events.publish('user.created', { id: userId, name: data.name });
  
  return res.status(201).json({ id: userId });
});

// WebSocket handlers
const chatHandler = (message, socket, metadata) => {
  req.logger.info({ userId: metadata.user?.id }, 'Chat message received');
  req.websocket.broadcastToRoom('chat', 'message', message.payload);
};

// Create the application
const app = createApp({
  name: 'example-service',
  port: 3000,
  
  // Mount routes
  routes: [
    { path: '/api/users', router: userRouter }
  ],
  
  // Configure WebSockets
  websocket: {
    path: '/ws',
    handlers: [
      { event: 'chat.message', handler: chatHandler }
    ]
  }
});

// Start the application
app.start().then(() => {
  app.logger.info(`🚀 ${app.config.get('app.name')} started on port ${app.config.get('app.port')}`);
});
```

## Testing

For detailed information about testing, please see [TESTING.md](TESTING.md).

## Docker Support

Orchard9 Express TSX comes with full Docker support for both development and production environments.

### Development Environment

```bash
# Start development environment
npm run docker:dev

# Build and start development environment
npm run docker:dev:build

# Run all tests (uses Docker)
npm test

# Stop containers
npm run docker:down

# Remove containers and volumes
npm run docker:clean
```

The development environment includes:
- Node.js application container with hot reloading
- PostgreSQL database
- Redis cache
- pgAdmin for database management (http://localhost:5050)
- Redis Commander for Redis inspection (http://localhost:8081)

### Production Environment

```bash
# Build and start production environment
npm run docker:prod

# Build production Docker image
npm run docker:build
```

The production environment:
- Uses multi-stage builds for smaller images
- Includes health checks for all services
- Configures resource limits for containers
- Supports environment variable configuration
- Implements security best practices

### Environment Variables

You can customize the Docker environment by creating a `.env` file based on the provided `.env.example`:

```bash
# Copy example environment file
cp .env.example .env

# Edit environment variables
nano .env

# Start with custom environment
npm run docker:dev
```

## License

MIT