ultimate-express

repository·main·Indexed 22 days ago

https://github.com/dimdengd/ultimate-express

A high-performance re-implementation of Express.js 4 built on uWebSockets.js. Designed as a drop-in replacement for Express, it offers higher throughput and lower latency while maintaining API compatibility. Features include HTTP/3 support, built-in async error handling, and a declarative route optimization system to bypass the Node.js event loop for simple responses.

Tokens
10.2K
Snippets
43
Records
57
Agent score
78%

What's inside ultimate-express

  1. Handle WebSockets in ultimate-express

    main

    Because µExpress manages the HTTP server internally, you cannot use http.on('upgrade'). You have two options for WebSockets:

    1. Use Ultimate WS: A sister library that implements a ws-compatible API designed for Ultimate Express upgrades.
    2. Direct uWS Access: Access the underlying uWebSockets.js App instance via app.uwsApp and call its .ws() method directly.
  2. Run the benchmark suite to compare express vs ultimate-express

    main

    Use the benchmark:compare script to run performance scenarios comparing the standard express framework against ultimate-express. You can specify the duration of the test and choose to output the results to a markdown file.

    npm run benchmark:compare -- --duration 20 --output benchmark_summary.md
  3. Configure HTTPS and HTTP/3 in ultimate-express

    main

    Unlike standard Express where you create an HTTPS server manually, µExpress requires you to pass uwsOptions directly to the express() constructor. This also applies to non-SSL HTTP; you should always use app.listen() instead of creating a manual server.

    To enable HTTP/3, set the http3 option to true.

    const express = require("ultimate-express");
    
    const app = express({
        http3: true,
        uwsOptions: {
            // See: https://unetworking.github.io/uWebSockets.js/generated/interfaces/AppOptions.html
            key_file_name: 'path/to/example.key',
            cert_file_name: 'path/to/example.crt'
        }
    });
    
    app.listen(3000, () => {
        console.log('Server is running on port 3000');
    });
  4. Install ultimate-express

    main

    To use µExpress as a drop-in replacement for Express.js, install it via npm.

    Requirements:

    • Node.js >= 22.0.0

    Version Compatibility:

    • For Node.js v19, v20, or v21: npm install ultimate-express@2.1.0
    • For Node.js v18: npm install ultimate-express@node-v18
    npm install ultimate-express
  5. Middleware compatibility and performance optimizations

    main

    µExpress is compatible with almost all Express-compatible middlewares. However, for optimal performance, you should use the built-in methods provided by µExpress instead of external middleware where available:

    • Instead of body-parser, use express.text() and similar built-in methods.
    • Instead of serve-static, use express.static().

    Confirmed incompatible middleware:

    • express-async-errors does not work. To handle asynchronous errors, use the built-in setting: app.set('catch async errors', true).
    // Instead of express-async-errors, use this:
    app.set('catch async errors', true);
  6. Run a specific benchmark scenario

    main

    To isolate performance testing to a single use case, use the --scenario flag with the benchmark:compare script. This is useful for testing specific patterns like middleware overhead, routing complexity, or streaming performance.

    npm run benchmark:compare -- --duration 20 --scenario hello-world
  7. Optimize performance in µExpress

    main

    To achieve maximum performance (up to 10x speedup on routes), follow these best practices:

    1. Enable Case Sensitive Routing: This is enabled by default in µExpress and allows the use of the native uWS router for string paths without regex characters (e.g., avoid *, +, (), {}).
    2. Use Built-in Middleware:
      • Use express.static() instead of the external serve-static module.
      • Use express.json(), express.text(), express.urlencoded(), or express.raw() instead of the body-parser module.
    3. Avoid unnecessary body parsing: Do not add GET to body methods unless strictly necessary.
    4. Manage Header Size: If you encounter issues with large headers, set the UWS_HTTP_MAX_HEADERS_SIZE environment variable (uWS defaults to 4096 bytes, while Node.js defaults to 16384 bytes).
  8. Use the Request object as a Readable stream

    main
    The Request object extends Node.js Readable stream. For methods that typically carry a body (like POST, PUT, PATCH, or methods defined in the app's body methods config), the request body is streamed through the Request instance. You can consume the body by listening to the data and end events or by using async iterators.
  9. Handle errors with error-handling middleware

    main

    Error-handling middleware is defined by using a callback function with four arguments: (err, req, res, next). When an error is passed to next(err), the router skips all remaining non-error middleware and searches for the next error-handling middleware in the stack.

    If no error handler is found, the router will log the error and send a default HTML error page with the stack trace.

    router.get('/broken', (req, res, next) => {
      next(new Error('Something went wrong!'));
    });
    
    // Error handler
    router.use((err, req, res, next) => {
      console.error(err.stack);
      res.status(500).send('Internal Server Error');
    });
  10. Declarative response data types and interpolation

    main

    compileDeclarative supports several ways to construct the response body, which are then mapped to uWS.DeclarativeResponse methods:

    • Static Text: Using string literals in .send() or .end().
    • Template Literals: Supports interpolation of req.params and req.query properties (e.g., `Hello ${req.params.id}`).
    • Member Expressions: Accessing specific properties like req.query.name or req.params.id directly.
    • Binary Expressions: Simple concatenation of literals and member expressions (e.g., req.query.name + '!').
    • JSON Objects: Passing a simple object literal to .send() will automatically set the Content-Type to application/json; charset=utf-8. Note that the object must be a simple literal and cannot contain non-literal values.
    // Template literal interpolation
    (req, res) => res.send(`User: ${req.params.id}`);
    
    // JSON object response
    (req, res) => res.send({ status: 'ok', code: 200 });
    
    // Member expression access
    (req, res) => res.send(req.query.search);
  11. Configure request body methods

    main

    By default, the request body is only read for POST, PUT, and PATCH requests. If you need to read the body for other methods (e.g., GET), you must set body methods to an array containing the uppercased method names.

    Note: Reading the body for methods that don't require it (like GET) can make endpoints approximately 15% slower.

  12. Requirements for declarative response callbacks

    main

    When using compileDeclarative, your callback function must adhere to a specific subset of JavaScript to be parsed into a uWS.DeclarativeResponse.

    Allowed Methods on res

    • set
    • header
    • setHeader
    • sendStatus
    • status
    • send
    • end
    • append

    Allowed Identifiers

    • query (from req)
    • params (from req)
    • Any of the allowed res methods listed above.

    Prohibited Syntax

    If your callback contains any of the following, it cannot be compiled declaratively:

    • Keywords: throw, new, await, return, try, catch, finally, if, else, switch, case, default, for, while, do, var, let, const.
    • External Calls: Any function call that is not a method on the res object.
    • Complex Logic: Variable declarations or complex object manipulations.