Skip to content

HTTP Controller

Use Cases

When you need to provide HTTP services in your application, use the HTTPController decorator to declare HTTP interfaces. It's recommended for scenarios that strongly depend on the HTTP protocol. Common scenarios include:

  • SSR scenarios, where HTML is rendered on the server side and returned to the frontend.
  • SSE scenarios, communicating with the frontend in real-time through Server-Sent Events to implement features like AI conversations.
  • Scenarios that rely on HTTP protocol data such as cookies for business logic processing.

Usage

Use the HTTPController decorator to declare a class as an HTTP controller, and use the HTTPMethod decorator to declare the specific HTTP interface information for methods in that class.

typescript
import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPParam } from 'egg';

@HTTPController()
export default class SimpleController {
  // Declare a GET /api/hello/:name interface
  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/hello/:name' })
  async hello(@HTTPParam() name: string) {
    return {
      message: 'hello ' + name,
    };
  }
}

The HTTPController decorator supports passing a path parameter to specify the base HTTP path for the controller, which will be concatenated with the path parameter in HTTPMethod to form the final HTTP path.

typescript
import { HTTPController, HTTPMethod, HTTPMethodEnum } from 'egg';

// Set path parameter to specify the path prefix for all interfaces in this class
@HTTPController({ path: '/api' })
export default class PathController {
  // GET /api/hello
  @HTTPMethod({ method: HTTPMethodEnum.GET, path: 'hello' })
  async hello() {
    // ...
  }

  // POST /api/echo
  @HTTPMethod({ method: HTTPMethodEnum.POST, path: 'echo' })
  async echo() {
    // ...
  }
}

Path Priority

The path set through the HTTPMethod decorator is parsed using path-to-regexp, which supports simple parameters, wildcards, and other features. When multiple HTTPMethod decorators satisfy path matching simultaneously, priority is needed to determine the matched interface. Interfaces with higher priority will be matched first.

Egg automatically calculates a priority for each interface. The default priority rules should satisfy most scenarios. Therefore, in most cases, there's no need to manually specify priority. The default priority rules are as follows:

priority = pathHasRegExp ? regexpIndexInPath.reduce((p,c) => p + c * 1000, 0) : 100000

Combined with specific examples, the default priorities of the following interfaces are shown from low to high:

PathRegExp indexpriority
/*[0]0
/hello/:name[1]1000
/hello/world/message/:message[3]3000
/hello/:name/message/:message[1, 3]4000
/hello/world[]100000

For business scenarios where the default priority is insufficient, you can manually specify priority through the priority parameter of the HTTPMethod decorator.

typescript
import { HTTPController, HTTPMethod, HTTPMethodEnum } from 'egg';

@HTTPController()
export default class PriorityController {
  @HTTPMethod({
    method: HTTPMethodEnum.GET,
    path: '/(api|openapi)/echo',
    priority: 100000, // Specify higher priority for this interface
  })
  async high() {
    // ...
  }

  @HTTPMethod({
    method: HTTPMethodEnum.POST,
    path: '/(api|openapi)/(.+)',
  })
  async low() {
    // ...
  }
}

Request Parameter Decorators

HTTPHeaders

The HTTPHeaders decorator is used to get the complete HTTP request headers.

WARNING

⚠️ Note: Keys in headers will be converted to lowercase. Please use lowercase characters when retrieving values.

typescript
import {
  HTTPController,
  HTTPMethod,
  HTTPMethodEnum,
  HTTPHeaders,
  IncomingHttpHeaders,
} from 'egg';

@HTTPController()
export default class ArgsController {
  // curl http://localhost:7001/api/hello -H 'X-Custom: custom'
  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/hello' })
  async getHeaders(@HTTPHeaders() headers: IncomingHttpHeaders) {
    const custom = headers['x-custom'];
    // ...
  }
}

HTTPQuery/HTTPQueries

The HTTPQuery/HTTPQueries decorators are used to get querystring parameters from HTTP requests. HTTPQuery only takes the first parameter and must be of type string; HTTPQueries injects parameters as an array containing one or more values, of type string[].

typescript
import {
  HTTPController,
  HTTPMethod,
  HTTPMethodEnum,
  HTTPQuery,
  HTTPQueries,
} from 'egg';

@HTTPController()
export default class ArgsController {
  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/query' })
  async getQueries(
    // /api/query?user=asd&user=fgh
    // user = 'asd'
    // users = ['asd', 'fgh']
    @HTTPQuery() user?: string, // When name is not set, variable name will be used automatically
    @HTTPQueries({ name: 'user' }) users?: string[], // Can also manually specify name
  ) {
    // ...
  }
}

HTTPParam

The HTTPParam decorator is used to get matched parameters from the HTTP request path, which can only be of string type. The parameter name is the same as the variable name by default, but can also be manually specified if there are alias requirements.

typescript
import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPParam } from 'egg';

@HTTPController()
export default class ArgsController {
  // curl http://127.0.0.1:7001/api/2088000
  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/:id' })
  async getParamId(@HTTPParam() id: string) {
    // id is '2088000'
    // ...
  }

  // Match the first regex-matched character in path
  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/foo/(.*)' })
  async getParamBar(@HTTPParam({ name: '0' }) bar: string) {
    // ...
  }
}

HTTPBody

The HTTPBody decorator is used to get request body content. When injecting, the framework will first parse the request body according to the content-type in the request header, supporting json, text, and form-urlencoded. Other content-type types will inject empty values. You can get the raw request body through the Request decorator and process it yourself.

typescript
import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPBody } from 'egg';

export interface BodyData {
  foo: string;
  bar?: number;
}

@HTTPController()
export default class ArgsController {
  // content-type: application/json
  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/json-body' })
  async getJsonBody(@HTTPBody() body: BodyData) {
    // ...
  }

  // content-type: text/plain
  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/text-body' })
  async getTextBody(@HTTPBody() body: string) {
    // ...
  }

  // content-type: application/x-www-form-urlencoded
  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/formdata-body' })
  async getFormBody(
    @HTTPBody() body: FormData, // In function apps, it's FormData type
    // @HTTPBody() body: BodyData, // In standard apps, it's a plain object
  ) {
    // ...
  }
}

Cookies

The Cookies decorator is used to get the complete HTTP Cookies.

typescript
import {
  Cookies,
  HTTPController,
  HTTPMethod,
  HTTPMethodEnum,
  HTTPCookies,
} from 'egg';

@HTTPController()
export default class ArgsController {
  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/cookies' })
  async getCookies(@HTTPCookies() cookies: Cookies) {
    return {
      success: true,
      cookies: cookies.get('test', { signed: false }),
    };
  }
}

HTTPRequest

The HTTPRequest decorator is used to get the complete HTTP request object, allowing you to get request information such as url, headers, and body. For specific APIs, please refer to the type definitions.

WARNING

⚠️ Note: After injecting the request body through the @HTTPBody decorator, the request body will be consumed. If you also inject @HTTPRequest and consume the request body again, it will cause an error (injecting @HTTPRequest without consuming the request body to get url, headers, etc. will not be affected).

typescript
import {
  HTTPBody,
  HTTPController,
  HTTPMethod,
  HTTPMethodEnum,
  HTTPRequest,
} from 'egg';

@HTTPController()
export default class ArgsController {
  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/request' })
  async getRequest(@HTTPRequest() request: Request) {
    const headerData = request.headers.get('x-header-key');
    const url = request.url;
    // Get request body arrayBuffer
    const arrayBufferData = await request.arrayBuffer();
    // ...
  }

  @HTTPMethod({ method: HTTPMethodEnum.POST, path: '/api/request2' })
  async getRequest2(@HTTPBody() body: object, @HTTPRequest() request: Request) {
    // Injecting both HTTPBody and Request, reading header, url, etc. through request works normally
    const headerData = request.headers.get('x-header-key');
    const url = request.url;
    // ❌ Wrong example
    // When the request body has already been injected through HTTPBody
    // Consuming the request body again through request will throw an exception
    // const arrayBufferData = await request.arrayBuffer();
    // ...
  }
}

HTTPContext

In standard applications, you can use the HTTPContext decorator to get the Egg Context object.

WARNING

⚠️ Note: The HTTPContext decorator is not supported in function applications.

typescript
import {
  HTTPContext,
  Context,
  HTTPController,
  HTTPMethod,
  HTTPMethodEnum,
} from 'egg';

@HTTPController()
export default class ArgsController {
  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/context' })
  async getContext(@HTTPContext() context: Context) {
    // ...
  }
}

HTTP Response

Default Response

By default, when the HTTPMethod function returns an object, the framework will process it with JSON.stringify and set Content-Type: application/json to return to the client.

typescript
import { HTTPController, HTTPMethod, HTTPMethodEnum } from 'egg';

@HTTPController()
export default class ResponseController {
  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/json' })
  async defaultResponse() {
    return {
      result: 'hello world',
    };
  }
}

Custom Response

Function Applications

In function applications, when you need to return non-JSON data or set HTTP response codes and response headers, you can set and return through the globally injected Response object.

typescript
import { HTTPController, HTTPMethod, HTTPMethodEnum } from 'egg';

@HTTPController()
export default class ResponseController {
  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/custom-response' })
  async customResponse() {
    // Response is a global object, no need to import
    return new Response('<h1>Hello World</h1>', {
      status: 200,
      headers: {
        'transfer-encoding': 'chunked',
        'content-type': 'text/html; charset=utf-8',
        'x-header-key': 'from-function',
      },
    });
  }
}

Standard Applications

In standard applications, you can use the APIs provided by Context to customize HTTP response codes and response headers.

typescript
import {
  Context,
  HTTPContext,
  HTTPController,
  HTTPMethod,
  HTTPMethodEnum,
} from 'egg';

@HTTPController()
export default class ResponseController {
  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/custom-response' })
  async customResponse(@HTTPContext() ctx: Context) {
    // Custom response code
    ctx.status = 200;
    // Add custom response header
    ctx.set('x-custom', 'custom');
    // Syntactic sugar for setting Content-Type, equivalent to ctx.set('content-type', 'application/json')
    // Supports common types like json, html, etc. See https://github.com/jshttp/mime-types
    ctx.type = 'html';

    return '<h1>Hello World</h1>';
  }
}

Stream Response

Simply wrap the streaming data as a Readable object and return it.

typescript
import { Readable } from 'node:stream';
import { setTimeout } from 'node:timers/promises';
import {
  Context,
  HTTPContext,
  HTTPController,
  HTTPMethod,
  HTTPMethodEnum,
} from 'egg';

// Construct streaming data
async function* generate(count = 5, duration = 500) {
  yield '<html><head><title>hello stream</title></head><body>';
  for (let i = 0; i < count; i++) {
    yield `<h2>Stream content ${i + 1}, ${Date()}</h2>`;
    await setTimeout(duration);
  }
  yield '</body></html>';
}

@HTTPController()
export default class ResponseController {
  @HTTPMethod({ method: HTTPMethodEnum.GET, path: '/api/stream' })
  async streamResponse(@HTTPContext() ctx: Context) {
    ctx.type = 'html';
    return Readable.from(generate());
  }
}

Born to build better enterprise frameworks and apps