2025-11-29 15:24:00 +00:00
2025-11-29 15:24:00 +00:00
2025-11-29 15:24:00 +00:00
2025-11-29 15:24:00 +00:00
2025-11-29 15:24:00 +00:00
2025-11-29 15:24:00 +00:00
2025-11-29 15:24:00 +00:00
2025-11-29 15:24:00 +00:00
2025-11-29 15:24:00 +00:00

@push.rocks/smartserve

A cross-platform HTTP server module for Node.js, Deno, and Bun with decorator-based routing, WebSocket support, static file serving, and WebDAV protocol. 🚀

Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.

Install

npm install @push.rocks/smartserve
# or
pnpm add @push.rocks/smartserve

Features

Cross-Platform - Works seamlessly on Node.js, Deno, and Bun 🎯 Decorator-Based Routing - Clean, expressive @Route, @Get, @Post decorators 🛡️ Guards & Interceptors - Built-in @Guard, @Transform, @Intercept for auth and transformation 📁 Static File Server - Streaming, ETags, Range requests, directory listing 🌐 WebDAV Support - Mount as network drive with full RFC 4918 compliance 🔌 WebSocket Ready - Native WebSocket support across all runtimes Zero Overhead - Native Web Standards API (Request/Response) on Deno/Bun 🔒 HTTPS/TLS - Built-in TLS support with certificate configuration

Quick Start

import { SmartServe, Route, Get, Post, Guard, type IRequestContext } from '@push.rocks/smartserve';

// Define a controller with decorators
@Route('/api')
class UserController {
  @Get('/hello')
  hello() {
    return { message: 'Hello World! 👋' };
  }

  @Get('/users/:id')
  getUser(ctx: IRequestContext) {
    return { id: ctx.params.id, name: 'John Doe' };
  }

  @Post('/users')
  createUser(ctx: IRequestContext<{ name: string; email: string }>) {
    return { id: 'new-id', ...ctx.body };
  }
}

// Create and start server
const server = new SmartServe({ port: 3000 });
server.register(UserController);
await server.start();

console.log('🚀 Server running at http://localhost:3000');

Decorators

Route Decorators

import { Route, Get, Post, Put, Delete, Patch, All } from '@push.rocks/smartserve';

@Route('/api/v1')  // Base path for all routes in this controller
class ApiController {
  @Get('/items')           // GET /api/v1/items
  listItems() { ... }

  @Get('/items/:id')       // GET /api/v1/items/:id - path parameters
  getItem(ctx: IRequestContext) {
    return { id: ctx.params.id };
  }

  @Post('/items')          // POST /api/v1/items
  createItem(ctx: IRequestContext<{ name: string }>) {
    return { created: ctx.body.name };
  }

  @Put('/items/:id')       // PUT /api/v1/items/:id
  updateItem(ctx: IRequestContext) { ... }

  @Delete('/items/:id')    // DELETE /api/v1/items/:id
  deleteItem(ctx: IRequestContext) { ... }

  @All('/webhook')         // Matches ALL HTTP methods
  handleWebhook(ctx: IRequestContext) { ... }
}

Guards (Authentication/Authorization)

Guards protect routes by returning true (allow) or false (reject with 403):

import { Route, Get, Guard, type IRequestContext } from '@push.rocks/smartserve';

// Guard function
const isAuthenticated = (ctx: IRequestContext) => {
  return ctx.headers.has('Authorization');
};

const isAdmin = (ctx: IRequestContext) => {
  return ctx.headers.get('X-Role') === 'admin';
};

// Apply guard to entire controller
@Route('/admin')
@Guard(isAuthenticated)
@Guard(isAdmin)  // Multiple guards - all must pass
class AdminController {
  @Get('/dashboard')
  dashboard() {
    return { admin: true };
  }

  // Method-level guard (runs after class guards)
  @Get('/super-secret')
  @Guard((ctx) => ctx.headers.get('X-Super') === 'yes')
  superSecret() {
    return { level: 'super-secret' };
  }
}

Transforms (Response Modification)

Transforms modify the response before sending:

import { Route, Get, Transform } from '@push.rocks/smartserve';

// Transform function
const wrapResponse = <T>(data: T) => ({
  success: true,
  data,
  timestamp: Date.now(),
});

const addVersion = <T extends object>(data: T) => ({
  ...data,
  apiVersion: '2.0',
});

@Route('/api')
@Transform(wrapResponse)  // Applied to all routes in controller
class ApiController {
  @Get('/info')
  @Transform(addVersion)  // Stacks with class transform
  getInfo() {
    return { name: 'MyAPI' };
  }
  // Response: { success: true, data: { name: 'MyAPI', apiVersion: '2.0' }, timestamp: ... }
}

Intercept (Full Control)

For complete control over request/response flow:

import { Route, Get, Intercept, type IRequestContext } from '@push.rocks/smartserve';

@Route('/api')
@Intercept({
  // Runs before handler
  request: async (ctx) => {
    console.log(`${ctx.method} ${ctx.path}`);
    // Return Response to short-circuit
    // Return modified context to continue
    // Return void to continue with original
  },
  // Runs after handler
  response: async (data, ctx) => {
    return { ...data, processedAt: new Date().toISOString() };
  },
})
class LoggedController {
  @Get('/data')
  getData() {
    return { items: [1, 2, 3] };
  }
}

Static File Server

Serve static files with streaming, ETags, and directory listing:

const server = new SmartServe({
  port: 3000,
  static: {
    root: './public',
    index: ['index.html', 'index.htm'],
    dotFiles: 'deny',           // 'allow' | 'deny' | 'ignore'
    etag: true,                 // Generate ETags
    lastModified: true,         // Add Last-Modified header
    cacheControl: 'max-age=3600',
    extensions: ['.html'],      // Try these extensions
    directoryListing: {
      showHidden: false,
      sortBy: 'name',           // 'name' | 'size' | 'modified'
      sortOrder: 'asc',
    },
  },
});

Or simply:

const server = new SmartServe({
  port: 3000,
  static: './public',  // Shorthand - uses defaults
});

WebDAV Support

Mount the server as a network drive on macOS, Windows, or Linux:

const server = new SmartServe({
  port: 8080,
  webdav: {
    root: '/path/to/files',
    auth: (ctx) => {
      // Optional Basic auth
      const auth = ctx.headers.get('Authorization');
      if (!auth) return false;
      const [, credentials] = auth.split(' ');
      const [user, pass] = atob(credentials).split(':');
      return user === 'admin' && pass === 'secret';
    },
    locking: true,  // Enable RFC 4918 file locking
  },
});

await server.start();
// Mount: Connect to Server → http://localhost:8080

Supported WebDAV Methods:

  • OPTIONS - Capability discovery
  • PROPFIND - Directory listing and file metadata
  • MKCOL - Create directory
  • COPY / MOVE - Copy and move operations
  • LOCK / UNLOCK - Exclusive write locking
  • GET / PUT / DELETE - File operations

WebSocket Support

WebSocket connections are handled natively:

const server = new SmartServe({
  port: 3000,
  websocket: {
    onOpen: (peer) => {
      console.log(`Connected: ${peer.id}`);
      peer.send('Welcome!');
    },
    onMessage: (peer, message) => {
      console.log(`Received: ${message.text}`);
      peer.send(`Echo: ${message.text}`);
    },
    onClose: (peer, code, reason) => {
      console.log(`Disconnected: ${peer.id}`);
    },
    onError: (peer, error) => {
      console.error(`Error: ${error.message}`);
    },
  },
});

HTTPS/TLS

Enable HTTPS with certificate configuration:

const server = new SmartServe({
  port: 443,
  tls: {
    cert: fs.readFileSync('./cert.pem'),
    key: fs.readFileSync('./key.pem'),
    // Optional
    ca: fs.readFileSync('./ca.pem'),
    minVersion: 'TLSv1.2',
  },
});

Error Handling

Built-in HTTP error classes:

import { HttpError, type IRequestContext } from '@push.rocks/smartserve';

@Route('/api')
class ApiController {
  @Get('/users/:id')
  async getUser(ctx: IRequestContext) {
    const user = await findUser(ctx.params.id);

    if (!user) {
      throw HttpError.notFound('User not found', { id: ctx.params.id });
    }

    return user;
  }
}

// Available factory methods:
HttpError.badRequest(message, details);      // 400
HttpError.unauthorized(message, details);    // 401
HttpError.forbidden(message, details);       // 403
HttpError.notFound(message, details);        // 404
HttpError.conflict(message, details);        // 409
HttpError.internal(message, details);        // 500

Global error handling:

const server = new SmartServe({
  port: 3000,
  onError: (error, request) => {
    console.error('Server error:', error);
    return new Response(JSON.stringify({ error: 'Something went wrong' }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' },
    });
  },
});

Request Context

Every handler receives a typed request context:

interface IRequestContext<TBody = unknown> {
  request: Request;              // Original Web Standards Request
  body: TBody;                   // Parsed and typed body
  params: Record<string, string>; // URL path parameters
  query: Record<string, string>; // Query string parameters
  headers: Headers;              // Request headers
  path: string;                  // Matched route path
  method: THttpMethod;           // HTTP method
  url: URL;                      // Full URL object
  runtime: 'node' | 'deno' | 'bun'; // Current runtime
  state: Record<string, unknown>; // Per-request state bag
}

Custom Request Handler

Bypass decorator routing entirely:

const server = new SmartServe({ port: 3000 });

server.setHandler(async (request, connectionInfo) => {
  const url = new URL(request.url);

  if (url.pathname === '/health') {
    return new Response('OK', { status: 200 });
  }

  return new Response('Not Found', { status: 404 });
});

await server.start();

Runtime Detection

SmartServe automatically detects and optimizes for the current runtime:

const instance = await server.start();

console.log(instance.runtime); // 'node' | 'deno' | 'bun'
console.log(instance.port);    // 3000
console.log(instance.secure);  // true if TLS enabled

This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the license file within this repository.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.

Company Information

Task Venture Capital GmbH Registered at District court Bremen HRB 35230 HB, Germany

For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

Description
a cross platform server
Readme 198 KiB
Languages
TypeScript 100%