Skip to content
/ server Public

The simplest, fastest, and most organized way to build production-ready servers with Bun.

License

Notifications You must be signed in to change notification settings

je-es/server

Repository files navigation


logo


CI Test Coverage Github Repo Issues GitHub Repo stars

  • Quick Start 🔥

    The simplest, fastest, and most organized way to build production-ready servers with Bun.

    We prefer to use space with @solution-dist/server for a better experience.

    • Setup

      install space first.

      • Create

        > space init <name> -t server # This will clone a ready-to-use repo and make some changes to suit your server.
        > cd <name>                   # Go to the project directory
        > space install               # Install the dependencies
      • Manage

        > space lint
        > space build
        > space test
        > space start
      • Usage

        import { server } from '@je-es/server';
        
        const app = server({
            port    : 3000,
            routes  : [
                {
                    method  : 'GET',
                    path    : '/',
                    handler : (c) => c.json({ message: 'Hello World!' })
                }
            ]
        });
        
        await app.start();
         > space start
         16:16:31 Server started at http://localhost:3000
         16:17:25 GET / 200 1ms
         ...
    line
  • Examples

    • Basic Server

      import { server } from '@je-es/server';
      
      const app = server({
          port    : 3000,
          logging : { level: 'info', pretty: true },
          routes  : [
              {
                  method  : 'GET',
                  path    : '/users/:id',
                  handler : (c) => c.json({
                      userId      : c.params.id,
                      ip          : c.ip,
                      requestId   : c.requestId
                  })
              },
              {
                  method          : 'POST',
                  path            : '/users',
                  handler         : (c) => c.status(201).json({
                      created : true,
                      data    : c.body
                  })
              }
          ]
      });
      
      await app.start();
      line
    • With Database

      import { server, table, integer, text, primaryKey, notNull } from '@je-es/server';
      
      const users = table('users', [
          primaryKey(integer('id'), true),
          notNull(text('name')),
          notNull(text('email'))
      ]);
      
      const app = server({
          port        : 3000,
          database    : {
              connection  : './app.db',
              schema      : { users }
          },
          routes: [
              {
                  method  : 'GET',
                  path    : '/users',
                  handler : (c) => c.json(c.db!.all('users'))
              },
              {
                  method  : 'POST',
                  path    : '/users',
                  handler : (c) => c.json(c.db!.insert('users', c.body))
              },
              {
                  method  : 'GET',
                  path    : '/users/:id',
                  handler : (c) => {
                      const user = c.db!.findById('users', parseInt(c.params.id));
                      return user
                          ? c.json(user)
                          : c.status(404).json({ error: 'Not found' });
                  }
              }
          ]
      });
      
      await app.start();
      line
    • With Security

      const app = server({
          port        : 3000,
          security    : {
              rateLimit   : { max: 100, windowMs: 60000 },
              cors        : {
                  origin      : ['http://localhost:3000'],
                  credentials : true
              }
          },
          routes: [/* your routes */]
      });
      line
    • Static Files

      const app = server({
          port    : 3000,
          static  : {
              path        : '/public',
              directory   : './public',
              maxAge      : 3600
          }
      });
    • Internationalization (i18n)

      const app = server({
          port    : 3000,
          i18n    : {
              defaultLanguage     : 'en',
              supportedLanguages  : ['en', 'ar', 'fr'],
              staticPath          : 'static/i18n'
          },
          routes: [
              {
                  method  : 'GET',
                  path    : '/message',
                  handler : (c) => {
                      // Language auto-detected from ?lang=ar, Accept-Language header, or defaults to 'en'
                      const greeting = c.i18n?.t('message.greeting', { name: 'John' });
                      return c.json({ greeting });
                  }
              }
          ]
      });

      Translation files (static/i18n/*.json):

      // en.json
      { "message.greeting": "Hello {name}" }
      
      // ar.json
      { "message.greeting": "مرحبا {name}" }

      Key Features:

      • Auto language detection from query params, headers, or defaults
      • Smart parameter replacement with nested translation key support
      • All supported languages loaded at server startup
      • Works with any number of languages dynamically

  • API

    • Server Configuration

      import { server, type ServerConfig } from '@je-es/server';
      
      const config: ServerConfig = {
          port                    : 3000,
          hostname                : 'localhost',
      
          // Timeouts & Limits
          requestTimeout          : 30000,
          maxRequestSize          : 10485760,
          gracefulShutdownTimeout : 10000,
      
          // Logging (via @je-es/slog)
          logging: {
              level               : 'info',       // 'debug' | 'info' | 'warn' | 'error'
              pretty              : false
          },
      
          // Database (via @je-es/sdb)
          database: {
              connection          : './app.db',   // or ':memory:'
              schema              : {}
          },
      
          // Multiple databases
          database: [
              { name: 'default',  connection: './main.db' },
              { name: 'cache',    connection: ':memory:' }
          ],
      
          // Security
          security: {
              rateLimit           : { max: 100, windowMs: 60000 },
              cors                : { origin: '*', credentials: true }
          },
      
          // Static files
          static: {
              path                : '/public',
              directory           : './public',
              maxAge              : 3600
          },
      
          // Lifecycle
          onStartup               : async (app) => { console.log('Starting...'); },
          onReady                 : async (app, db) => { console.log('Server ready with DB'); },
          onShutdown              : async () => { console.log('Shutting down...'); }
      };
      line
    • Context API

      handler : (c: AppContext) => {
          // Request
          c.params        // URL parameters
          c.query         // Query string
          c.body          // Parsed body (JSON/form/multipart)
          c.headers       // Request headers
          c.ip            // Client IP
          c.requestId     // Unique request ID
      
          // Resources
          c.db            // Database instance
          c.logger        // Logger instance
      
          // Response
          c.json({ data })
          c.text('text')
          c.html('HTML')
          c.redirect('/path')
          c.file('./file.pdf', 'application/pdf')
          c.status(201).json({ created: true })
      
          // Headers
          c.setHeader('X-Custom', 'value')
          c.getHeader('Authorization')
      
          // Cookies
          c.setCookie('session', 'token', { httpOnly: true })
          c.getCookie('session')
          c.deleteCookie('session')
      }
      line
    • Routes

      // Single method
      { method  : 'GET', path    : '/users', handler }
      
      // Multiple methods
      { method  : ['GET', 'POST'], path    : '/api', handler }
      
      // Dynamic parameters
      { method  : 'GET', path    : '/users/:id', handler }
      { method  : 'GET', path    : '/posts/:postId/comments/:commentId', handler }
      
      // Wildcards
      { method  : 'GET', path    : '/files/*', handler }
      line
    • Database Operations

      // CRUD
      c.db!.all       ('users')
      c.db!.findById  ('users', 1)
      c.db!.find      ('users',    { role: 'admin' })
      c.db!.insert    ('users',    { name: 'John'  })
      c.db!.update    ('users', 1, { name: 'Jane'  })
      c.db!.delete    ('users', 1)
      
      // Multiple databases
      const mainDb = app.db.get('default');
      const cacheDb = app.db.get('cache');
      line
    • Dynamic Routes

      await app.start();
      
      // Add single route
      app.addRoute({
          method  : 'POST',
          path    : '/dynamic',
          handler : (c) => c.json({ dynamic: true })
      });
      
      // Add multiple routes
      app.addRoutes([
          { method  : 'GET', path    : '/route1', handler },
          { method  : 'GET', path    : '/route2', handler }
      ]);
      
      // Get all routes
      const routes = app.getRoutes();

  • Security

    • Rate Limiting

      security: {
          rateLimit: {
              max             : 100,
              windowMs        : 60000,
              keyGenerator    : (c) => c.ip,
              message         : 'Too many requests'
          }
      }
      line
    • CORS

      security: {
          cors: {
              origin          : ['http://localhost:3000'],
              // or: origin   : '*',
              // or: origin   : (origin) => origin.endsWith('.example.com'),
              methods         : ['GET', 'POST', 'PUT', 'DELETE'],
              credentials     : true
          }
      }
      line
    • Input Sanitization

      import { SecurityManager } from '@je-es/server';
      
      const security = new SecurityManager();
      
      security.sanitizeHtml('xss');
      security.sanitizeSql("'; DROP TABLE users--");
      line
    • CSRF Protection

      const security  = new SecurityManager();
      const token     = security.generateCsrfToken('session-id');
      const valid     = security.validateCsrfToken(token, 'session-id');

  • Error Handling

    import {
        AppError,           // Custom errors
        ValidationError,    // 400
        DatabaseError,      // 500
        TimeoutError,       // 408
        RateLimitError      // 429
    } from '@je-es/server';
    
    handler : (c) => {
        if (!c.body?.email) {
            throw new ValidationError('Email required');
        }
    
        if (invalid) {
            throw new AppError('Invalid data', 400, 'INVALID_DATA');
        }
    
        return c.json({ success: true });
    }

  • Built-in Endpoints

    // Health check
    GET /health
    // Response: { status, timestamp, uptime, activeRequests }
    
    // Readiness check
    GET /readiness
    // Response: { ready, checks: { database, activeRequests }, timestamp }

  • Advanced

    • Cookie Management

      c.setCookie('session', 'token', {
          maxAge      : 3600,
          httpOnly    : true,
          secure      : true,
          sameSite    : 'Strict',
          path        : '/',
          domain      : 'example.com'
      });
      line
    • Static File Options

      static: {
          path            : '/public',
          directory       : './public',
          maxAge          : 3600,
          index           : ['index.html'],
          dotfiles        : 'deny', // 'allow' | 'deny' | 'ignore'
          etag            : true,
          lastModified    : true,
          immutable       : false,
          extensions      : ['html', 'htm'],
          setHeaders      : (ctx, path) => {
              ctx.setHeader('X-Custom', 'value');
          }
      }
      line
    • Startup & Shutdown Hooks

      const app = server({
          gracefulShutdownTimeout: 10000,
          onStartup: async (app) => {
              // Initialize database, admin user, etc.
              console.log('✓ Server initialized');
          },
          onShutdown: async () => {
              // Cleanup
              console.log('Server shutting down gracefully...');
          }
      });
      
      process.on('SIGTERM', async () => {
          await app.stop();
          process.exit(0);
      });
      line
    • onReady Hook (Database Initialization)

      Use onReady to perform operations with the fully initialized server and databases. Unlike onStartup, onReady provides access to database instances.

      const app = server({
          database: {
              connection: './app.db',
              schema: { users }
          },
          onReady: async (app, db) => {
              // Databases are now fully initialized
              const defaultDb = db.get('default');
              
              // Seed initial data
              defaultDb?.insert('users', { name: 'Admin', email: 'admin@example.com' });
              
              // Add routes dynamically
              app.addRoute({
                  method: 'GET',
                  path: '/api/health',
                  handler: (c) => c.json({ status: 'ok' })
              });
              
              console.log('✓ Database initialization complete');
          }
      });
      line
    • Logging

      handler : (c) => {
          c.logger?.info( { userId: 123  }, 'User action');
          c.logger?.warn( { attempt: 3   }, 'Warning');
          c.logger?.error({ error: 'msg' }, 'Error');
      }

  • Complete Example

    import { server, table, integer, text, primaryKey, notNull } from '@je-es/server';
    
    const users = table('users', [
        primaryKey(integer('id'), true),
        notNull(text('name')),
        notNull(text('email'))
    ]);
    
    const app = server({
        port        : 3000,
        logging     : { level: 'info', pretty: true },
        database    : { connection: './app.db', schema: { users } },
        security    : {
            rateLimit   : { max: 100, windowMs: 60000 },
            cors        : { origin: ['http://localhost:3000'] }
        },
        static      : { path: '/public', directory: './public' },
        routes      : [
            {
                method  : 'GET',
                path    : '/api/users',
                handler : (c) => c.json(c.db!.all('users'))
            },
            {
                method  : 'POST',
                path    : '/api/users',
                handler : (c) => {
                    if (!c.body?.name || !c.body?.email) {
                        throw new ValidationError('Name and email required');
                    }
                    const user = c.db!.insert('users', c.body);
                    return c.status(201).json(user);
                }
            },
            {
                method  : 'GET',
                path    : '/api/users/:id',
                handler : (c) => {
                    const user = c.db!.findById('users', parseInt(c.params.id));
                    return user ? c.json(user) : c.status(404).json({ error: 'Not found' });
                }
            }
        ]
    });
    
    await app.start();


About

The simplest, fastest, and most organized way to build production-ready servers with Bun.

Topics

Resources

License

Stars

Watchers

Forks