File: README.md
English | ็ฎไฝไธญๆ
Features
- Built-in Process Management
- Plugin System
- Framework Customization
- Lots of plugins
Quickstart
Follow the commands listed below.
$ corepack enable utoo
$ mkdir showcase && cd showcase
$ ut create egg@beta
$ ut install
$ ut run dev
$ open http://localhost:7001Node.js >= 22.18.0 required.
Monorepo Structure
This project is structured as a utoo monorepo with the following packages:
packages/egg- Main Eggjs frameworkexamples/helloworld-commonjs- CommonJS example applicationexamples/helloworld-typescript- TypeScript example applicationsite- Documentation website
The monorepo uses utoo catalog mode for centralized dependency management, ensuring consistent versions across all packages.
Development Commands
# Install dependencies for all packages
ut install --from pnpm
# Build all packages
ut run build
# Test all packages
ut run test
# Run specific package commands
ut --filter=egg run test
ut --filter=@examples/helloworld-typescript run dev
ut --filter=site run devLocal External Services
Some DAL, ORM, Redis, and ecosystem benchmark paths need local MySQL and Redis services. Start the repository-aligned Docker services before running those tests on a clean machine:
ut run dev:services:startThis starts MySQL 8 and Redis 7, matching the main CI service versions, and creates the databases used by local DAL/ORM/e2e fixtures: test, apple, banana, test_runtime_datasource, test_runtime_dao, test_dal_plugin, test_dal_standalone, cnpmcore, and cnpmcore_unittest.
Useful commands:
ut run dev:services:status
ut run dev:services:stop
ut run dev:services:resetThe default host ports are 127.0.0.1:3306 for MySQL and 127.0.0.1:6379 for Redis. If either port is already used, the start command stops before changing containers. Keep using the existing service if it is compatible with CI, or stop it and run the command again. You can change Docker host ports with EGG_DEV_SERVICES_MYSQL_PORT and EGG_DEV_SERVICES_REDIS_PORT; however, the full DAL/ORM/Redis local test path still expects the default host ports.
Image overrides are available for compatibility checks:
EGG_DEV_SERVICES_MYSQL_IMAGE=mysql:5.7 ut run dev:services:start
EGG_DEV_SERVICES_REDIS_IMAGE=redis:7 ut run dev:services:startRun ut run dev:services:reset before switching MySQL image families, for example between MySQL 8 and MySQL 5.7, because MySQL data directories are not downgrade-compatible across major versions.
Current hard-coded service assumptions:
- Redis plugin fixtures under
plugins/redis/test/fixtures/apps/**/config.*use127.0.0.1:6379; skipped Redis plugin tests become runnable when that port is available. - Session Redis fixtures under
plugins/session/test/fixtures/redis-session/config/config.default.jsuse127.0.0.1:6379. - DAL runtime tests in
tegg/core/dal-runtime/test/DataSource.test.tsandtegg/core/dal-runtime/test/DAO.test.tsuse local MySQL on port3306. - DAL module fixtures in
tegg/plugin/dal/test/fixtures/apps/dal-app/modules/dal/module.ymlandtegg/standalone/standalone/test/fixtures/dal-*/module.ymluse local MySQL on port3306. - ORM fixtures in
tegg/plugin/orm/test/fixtures/prepare.jsandtegg/plugin/orm/test/fixtures/apps/orm-app/config/config.default.tsuse local MySQL on port3306.
Documentations
Contributors
How to Contribute
Please let us know how can we help. Do check out issues for bug reports or suggestions first.
To become a contributor, please follow our contributing guide, and review the repository guidelines for day-to-day development tips.
Sponsors and Backers
License
File: benchmark/ci-test/README.md
CI Test Benchmark
ut run benchmark:ci-test runs the reusable CI test benchmark harness and writes a Markdown report, a JSON summary, and the raw Vitest JSON reporter output.
Environment Setup
- Use Node.js
>=22.18.0. - Make sure the Utoo CLI is available as
ut. The CI workflow usesutooland/setup-utoobefore dependency installation. - Install workspace dependencies from the repository root with
ut install --from pnpm, matching the CI workflow. - Run commands from the repository root so workspace paths and
vitest.config.tsdefaults can be detected. - Keep Redis and MySQL available when benchmarking suites that require them. The CI test job uses Redis 7 on the default Redis port and MySQL 8 with a
testdatabase; the benchmark harness mirrors the CI Vitest flags but does not start external services. - For CI-like metadata, set the relevant environment variables before running the command, for example
CI=1,GITHUB_SHA,RUNNER_OS, or worker-relatedVITEST_*variables.
Usage
# Run the suite and benchmark it
ut run benchmark:ci-test
ut run benchmark:ci-test -- --coverage
ut run benchmark:ci-test -- --output-dir .tmp/ci-benchmark -- ut execute vitest run packages/extend2/test/index.test.ts
# Summarize an existing Vitest JSON without running tests (the CI path)
ut run benchmark:ci-test -- --report-only --vitest-json benchmark/ci-test/ci-run/vitest-results.jsonThe default output directory is benchmark/ci-test/<timestamp>. Use --output-dir for a deterministic path when collecting artifacts.
Parallelism metrics
Tests run with isolate: false (full parallelism; the tegg TeggScope per-app isolation keeps concurrent multi-app boots safe). The report reconstructs the concurrency timeline from each file's Vitest interval (min test start .. max test end) and reports:
- Execution window โ
max(end) - min(start), the wall-clock span of test execution. - Busy time โ sum of per-file spans (the area under the concurrency curve).
- Avg concurrency โ
busy time รท execution window, the time-weighted mean number of files running at once. - Peak concurrency โ the maximum number of files whose intervals overlap (sweep line). The robust headline signal.
- Parallel efficiency โ
avg concurrency รท worker ceiling(the ceiling mirrorsvitest.config.ts: Windows CI caps workers, otherwise the machine's available parallelism). - Critical path โ the longest single-file span (the wall-clock floor with unlimited workers).
Interval caveat: the Vitest 4 JSON reporter derives a file's
startTime/endTimefrom test-level timings only, so the span covers test bodies and per-testbeforeEach/afterEachbut excludes suite-levelbeforeAll/afterAll(where egg boots its apps โ often the dominant per-file cost) and module transform/import. That excluded time still occupies the worker threads, so avg concurrency and parallel efficiency are lower bounds on real worker utilization; forbeforeAll-heavy suites avg can read below 1 while peak is high. Use peak concurrency as the primary signal. Fully-skipped files (no test timings) are dropped from the calculation.
CI integration
The test gating job in .github/workflows/ci.yml is instrumented without changing gate semantics:
vitest.config.tsadds ajsonreporter whenCIis set, writingbenchmark/ci-test/ci-run/vitest-results.jsonduring the gatingut run cirun.- A
Report parallelism metricsstep (if: always()) runs the harness in--report-onlymode against that JSON. - When
GITHUB_STEP_SUMMARYis set, the Markdown report (including the parallelism table) is appended to the GitHub Actions job summary, so the metrics are visible on the run page per OS/Node matrix entry.
Gating still comes entirely from ut run ci; the metrics step is informational and exits 0 even when the JSON is missing.
Outputs
report.md: human-readable benchmark report (run, environment, parameters, parallelism, long-tail tables).report.json: structured report containing environment, command, wall time, Vitest summary, parallelism metrics, long-tail file/project durations, and coverage/worker/isolate parameters.vitest-results.json: raw Vitest JSON reporter output.
File: packages/utils/README.md
@eggjs/utils
Utils for all egg projects.
Installation
npm i @eggjs/utilsAPI
`getPlugins(options)`
- {String} baseDir - the current directory of application
- {String} framework - the directory of framework
- {String} env - egg environment
`getLoadUnits(options)`
- {String} baseDir - the current directory of application
- {String} framework - the directory of framework
- {String} env - egg environment
`getConfig(options)`
- {String} baseDir - the current directory of application
- {String} framework - the directory of framework
- {String} env - egg environment
`getFrameworkPath(options)`
- {String} baseDir - the current directory of application
- {String} framework - the directory of framework
`setBundleModuleLoader(loader)`
Register a module loader hook for bundled Egg apps. The hook runs before the
normal importModule() resolution path.
- {Function | undefined} loader - a synchronous function that receives the
originalfilepathargument passed toimportModule()after POSIX separator
normalization, or a virtual specifier. It does not receive the resolved
absolute file path fromimportResolve(). Returnundefinedto fall back to
the normal import path.
The bundle loader is stored on globalThis, so bundled and external copies of@eggjs/utils share the same loader. Non-undefined results follow the same
default export unwrapping rules as importModule(), includingimportDefaultOnly.
Bundle / snapshot module-loading hooks
importModule() resolves a module through the following hooks, in order. The
first one that produces a value wins; otherwise it falls back to the nativeimport() / require() path:
globalThis.__EGG_BUNDLE_MODULE_LOADER__โ set viasetBundleModuleLoader(). Looks up a module already inlined into the bundle
(typically a static bundle map emitted byegg-bundler). Runs before on-disk
resolution and receives the POSIX-normalizedimportModule()filepath or a
virtual specifier. Returnundefinedto fall through.- Snapshot module loader โ set via
setSnapshotModuleLoader(). This is a
module-local hook (not aglobalThisglobal) used by the V8 snapshot entry
generator to serve pre-bundled modules synchronously, keyed by the resolved
path. Once registered it handles every load that reaches it, so the importer
below is not consulted while it is active. globalThis.__EGG_MODULE_IMPORTER__โ an async (or sync, since the value
is awaited) importer that receives the resolved file path (theimportResolve()result, with OS-native separators โ not normalized). When
set, and the two hooks above did not resolve the module, it replaces the
nativeawait import(filePath).
The bundle loader and importer globals are typed in @eggjs/typings
(BundleModuleLoader / ModuleImporter); import @eggjs/typings/global to pick
up the declare global augmentation. These hooks are the contract thategg-bundler's generated entry relies on. @eggjs/core's ManifestLoaderFS
consults __EGG_BUNDLE_MODULE_LOADER__ directly; its importer/native fallback is
reached through @eggjs/loader-fs, which calls back into importModule(). The
tegg loader (LoaderUtil.loadFile) consults both globals directly, passing the
loader filepath with separators normalized to POSIX.
__EGG_MODULE_IMPORTER__ has two main uses:
- Bundler-based test runners (e.g. Vitest): route module loading through the
runner's own module graph so the loader and the test file share a single
module instance (otherwisectx.getEggObject(ClassRef)fails with
"can not get proto"). - V8 startup-snapshot restore: the deserialized main function runs without a
host dynamic-import callback, so nativeimport()throws. The snapshot entry
installs a synchronousrequire()-based importer (createRequire()over the
bundle output dir);require()can load ESM on Node >= 22, so modules resolve
without dynamic import.
License
Contributors
Made with contributors-img.
File: packages/tsconfig/README.md
@eggjs/tsconfig
Base tsconfig file for egg project
Install
npm i --save-dev @eggjs/tsconfigUsage
// tsconfig.json
{
"extends": "@eggjs/tsconfig",
// custom config
"compilerOptions": {
// override @eggjs/tsconfig options here
}
}License
Contributors
Made with contributors-img.
File: packages/supertest/README.md
@eggjs/supertest
HTTP assertions made easy via superagent. Maintained for Forward Email and Lad.
Forked for TypeScript friendly
Document see SuperTest
About
The motivation with this module is to provide a high-level abstraction for testing
HTTP, while still allowing you to drop down to the lower-level API provided by superagent.
Getting Started
Install SuperTest as an npm module and save it to your package.json file as a development dependency:
npm install @eggjs/supertest --save-devOnce installed it can now be referenced by simply calling require('supertest');
Example
You may pass an http.Server, or a Function to request() - if the server is not
already listening for connections then it is bound to an ephemeral port for you so
there is no need to keep track of ports.
SuperTest works with any test framework, here is an example without using any
test framework at all:
const { request } = require('@eggjs/supertest');
const express = require('express');
const app = express();
app.get('/user', function (req, res) {
res.status(200).json({ name: 'john' });
});
request(app)
.get('/user')
.expect('Content-Type', /json/)
.expect('Content-Length', '15')
.expect(200)
.end(function (err, res) {
if (err) throw err;
});To enable http2 protocol, simply append an options to request or request.agent:
const { request } = require('@eggjs/supertest');
const express = require('express');
const app = express();
app.get('/user', function (req, res) {
res.status(200).json({ name: 'john' });
});
request(app, { http2: true })
.get('/user')
.expect('Content-Type', /json/)
.expect('Content-Length', '15')
.expect(200)
.end(function (err, res) {
if (err) throw err;
});
request
.agent(app, { http2: true })
.get('/user')
.expect('Content-Type', /json/)
.expect('Content-Length', '15')
.expect(200)
.end(function (err, res) {
if (err) throw err;
});Here's an example with mocha, note how you can pass done straight to any of the .expect() calls:
describe('GET /user', function () {
it('responds with json', function (done) {
request(app).get('/user').set('Accept', 'application/json').expect('Content-Type', /json/).expect(200, done);
});
});You can use auth method to pass HTTP username and password in the same way as in the superagent:
describe('GET /user', function () {
it('responds with json', function (done) {
request(app)
.get('/user')
.auth('username', 'password')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200, done);
});
});One thing to note with the above statement is that superagent now sends any HTTP
error (anything other than a 2XX response code) to the callback as the first argument if
you do not add a status code expect (i.e. .expect(302)).
If you are using the .end() method .expect() assertions that fail will
not throw - they will return the assertion as an error to the .end() callback. In
order to fail the test case, you will need to rethrow or pass err to done(), as follows:
describe('POST /users', function () {
it('responds with json', function (done) {
request(app)
.post('/users')
.send({ name: 'john' })
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) return done(err);
return done();
});
});
});You can also use promises:
describe('GET /users', function () {
it('responds with json', function () {
return request(app)
.get('/users')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200)
.then((response) => {
expect(response.body.email).toEqual('[email protected]');
});
});
});Or async/await syntax:
describe('GET /users', function () {
it('responds with json', async function () {
const response = await request(app).get('/users').set('Accept', 'application/json');
expect(response.headers['Content-Type']).toMatch(/json/);
expect(response.status).toEqual(200);
expect(response.body.email).toEqual('[email protected]');
});
});Expectations are run in the order of definition. This characteristic can be used
to modify the response body or headers before executing an assertion.
describe('POST /user', function () {
it('user.name should be an case-insensitive match for "john"', function (done) {
request(app)
.post('/user')
.send('name=john') // x-www-form-urlencoded upload
.set('Accept', 'application/json')
.expect(function (res) {
res.body.id = 'some fixed id';
res.body.name = res.body.name.toLowerCase();
})
.expect(
200,
{
id: 'some fixed id',
name: 'john',
},
done,
);
});
});Anything you can do with superagent, you can do with supertest - for example multipart file uploads!
request(app)
.post('/')
.field('name', 'my awesome avatar')
.field('complex_object', '{"attribute": "value"}', {contentType: 'application/json'})
.attach('avatar', 'test/fixtures/avatar.jpg')
...Passing the app or url each time is not necessary, if you're testing
the same host you may simply re-assign the request variable with the
initialization app or url, a new Test is created per request.VERB() call.
t = request('http://localhost:5555');
t.get('/').expect(200, function (err) {
console.log(err);
});
t.get('/').expect('heya', function (err) {
console.log(err);
});Here's an example with mocha that shows how to persist a request and its cookies:
const { agent } = require('@eggjs/supertest');
const should = require('should');
const express = require('express');
const cookieParser = require('cookie-parser');
describe('request.agent(app)', function () {
const app = express();
app.use(cookieParser());
app.get('/', function (req, res) {
res.cookie('cookie', 'hey');
res.send();
});
app.get('/return', function (req, res) {
if (req.cookies.cookie) res.send(req.cookies.cookie);
else res.send(':(');
});
const testAgent = agent(app);
it('should save cookies', function (done) {
testAgent.get('/').expect('set-cookie', 'cookie=hey; Path=/', done);
});
it('should send cookies', function (done) {
testAgent.get('/return').expect('hey', done);
});
});There is another example that is introduced by the file agency.js
Here is an example where 2 cookies are set on the request.
agent(app)
.get('/api/content')
.set('Cookie', ['nameOne=valueOne;nameTwo=valueTwo'])
.send()
.expect(200)
.end((err, res) => {
if (err) {
return done(err);
}
expect(res.text).to.be.equal('hey');
return done();
});API
You may use any superagent methods,
including .write(), .pipe() etc and perform assertions in the .end() callback
for lower-level needs.
.expect(status[, fn])
Assert response status code.
.expect(status, body[, fn])
Assert response status code and body.
.expect(body[, fn])
Assert response body text with a string, regular expression, or
parsed body object.
.expect(field, value[, fn])
Assert header field value with a string or regular expression.
.expect(function(res) {})
Pass a custom assertion function. It'll be given the response object to check. If the check fails, throw an error.
request(app).get('/').expect(hasPreviousAndNextKeys).end(done);
function hasPreviousAndNextKeys(res) {
if (!('next' in res.body)) throw new Error('missing next key');
if (!('prev' in res.body)) throw new Error('missing prev key');
}.end(fn)
Perform the request and invoke fn(err, res).
Notes
Inspired by api-easy minus vows coupling.
License
Contributors
Made with contributors-img.
File: packages/router/README.md
@eggjs/router
Router core component for Egg.js.
This repository is a fork of koa-router. with some additional features.
And thanks for the great work of @alexmingoia and the original team.
API Reference
- @eggjs/router
- API Reference
- Router โ
- Nested routers
- Router prefixes
- URL parameters
- router.routes โ function
- router.use([path], middleware) โ Router
- router.prefix(prefix) โ Router
- router.allowedMethods([options]) โ function
- router.redirect(source, destination, [code]) โ Router
- router.route(name) โ Layer | false
- router.url(name, params, [options]) โ String | Error
- router.param(param, middleware) โ Router
- Router.url(path, params [, options]) โ String
- Tests
- Breaking changes on v3
- License
- Contributors
- API Reference
Router โ
new Router([opts])
Create a new router.
| Param | Type | Description |
|---|---|---|
| [opts] | Object |
|
| [opts.prefix] | String |
prefix router paths |
Example
Basic usage:
import Koa from '@eggjs/koa';
import Router from '@eggjs/router';
const app = new Koa();
const router = new Router();
router.get('/', async (ctx, next) => {
// ctx.router available
});
app.use(router.routes()).use(router.allowedMethods());router.get|put|post|patch|delete|del โ `Router`
Create router.verb() methods, where verb is one of the HTTP verbs such
as router.get() or router.post().
Match URL patterns to callback functions or controller actions using router.verb(),
where verb is one of the HTTP verbs such as router.get() or router.post().
Additionaly, router.all() can be used to match against all methods.
router
.get('/', (ctx, next) => {
ctx.body = 'Hello World!';
})
.post('/users', (ctx, next) => {
// ...
})
.put('/users/:id', (ctx, next) => {
// ...
})
.del('/users/:id', (ctx, next) => {
// ...
})
.all('/users/:id', (ctx, next) => {
// ...
});When a route is matched, its path is available at ctx.routePath and if named,
the name is available at ctx.routeName
Route paths will be translated to regular expressions using
path-to-regexp.
Query strings will not be considered when matching requests.
Named routes
Routes can optionally have names. This allows generation of URLs and easy
renaming of URLs during development.
router.get('user', '/users/:id', (ctx, next) => {
// ...
});
router.url('user', 3);
// => "/users/3"Multiple middleware
Multiple middleware may be given:
router.get(
'/users/:id',
(ctx, next) => {
return User.findOne(ctx.params.id).then(function (user) {
ctx.user = user;
next();
});
},
(ctx) => {
console.log(ctx.user);
// => { id: 17, name: "Alex" }
},
);Nested routers
Nesting routers is supported:
const forums = new Router();
const posts = new Router();
posts.get('/', (ctx, next) => {...});
posts.get('/:pid', (ctx, next) => {...});
forums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods());
// responds to "/forums/123/posts" and "/forums/123/posts/123"
app.use(forums.routes());Router prefixes
Route paths can be prefixed at the router level:
const router = new Router({
prefix: '/users'
});
router.get('/', ...); // responds to "/users"
router.get('/:id', ...); // responds to "/users/:id"URL parameters
Named route parameters are captured and added to ctx.params.
router.get('/:category/:title', (ctx, next) => {
console.log(ctx.params);
// => { category: 'programming', title: 'how-to-node' }
});The path-to-regexp module is
used to convert paths to regular expressions.
Kind: instance property of [Router](#exp_module_egg-router--Router)
| Param | Type | Description |
|---|---|---|
| path | String |
|
| [middleware] | function |
route middleware(s) |
| callback | function |
route callback |
router.routes โ `function`
Returns router middleware which dispatches a route matching the request.
Kind: instance property of [Router](#exp_module_egg-router--Router)
router.use([path], middleware) โ `Router`
Use given middleware.
Middleware run in the order they are defined by .use(). They are invoked
sequentially, requests start at the first middleware and work their way
"down" the middleware stack.
Kind: instance method of [Router](#exp_module_egg-router--Router)
| Param | Type |
|---|---|
| [path] | String |
| middleware | function |
| [...] | function |
Example
// session middleware will run before authorize
router.use(session()).use(authorize());
// use middleware only with given path
router.use('/users', userAuth());
// or with an array of paths
router.use(['/users', '/admin'], userAuth());
app.use(router.routes());router.prefix(prefix) โ `Router`
Set the path prefix for a Router instance that was already initialized.
Kind: instance method of [Router](#exp_module_egg-router--Router)
| Param | Type |
|---|---|
| prefix | String |
Example
router.prefix('/things/:thing_id');router.allowedMethods([options]) โ `function`
Returns separate middleware for responding to OPTIONS requests with
an Allow header containing the allowed methods, as well as responding
with 405 Method Not Allowed and 501 Not Implemented as appropriate.
Kind: instance method of [Router](#exp_module_egg-router--Router)
| Param | Type | Description |
|---|---|---|
| [options] | Object |
|
| [options.throw] | Boolean |
throw error instead of setting status and header |
| [options.notImplemented] | function |
throw the returned value in place of the default NotImplemented error |
| [options.methodNotAllowed] | function |
throw the returned value in place of the default MethodNotAllowed error |
Example
import Koa from '@eggjs/koa';
import Router from '@eggjs/router';
const app = new Koa();
const router = new Router();
app.use(router.routes());
app.use(router.allowedMethods());Example with Boom
import Koa from '@eggjs/koa';
import Router from '@eggjs/router';
import Boom from 'boom';
const app = new Koa();
const router = new Router();
app.use(router.routes());
app.use(
router.allowedMethods({
throw: true,
notImplemented: () => new Boom.notImplemented(),
methodNotAllowed: () => new Boom.methodNotAllowed(),
}),
);router.redirect(source, destination, [code]) โ `Router`
Redirect source to destination URL with optional 30x status code.
Both source and destination can be route names.
router.redirect('/login', 'sign-in');This is equivalent to:
router.all('/login', (ctx) => {
ctx.redirect('/sign-in');
ctx.status = 301;
});Kind: instance method of [Router](#exp_module_egg-router--Router)
| Param | Type | Description |
|---|---|---|
| source | String |
URL or route name. |
| destination | String |
URL or route name. |
| [code] | Number |
HTTP status code (default: 301). |
router.route(name) โ `Layer` | `false`
Lookup route with given name.
Kind: instance method of [Router](#exp_module_egg-router--Router)
| Param | Type |
|---|---|
| name | String |
router.url(name, params, [options]) โ `String` | `Error`
Generate URL for route. Takes a route name and map of named params.
Kind: instance method of [Router](#exp_module_egg-router--Router)
| Param | Type | Description |
|---|---|---|
| name | String |
route name |
| params | Object |
url parameters |
| [options] | Object |
options parameter |
| [options.query] | Object | String |
query options |
Example
router.get('user', '/users/:id', (ctx, next) => {
// ...
});
router.url('user', 3);
// => "/users/3"
router.url('user', { id: 3 });
// => "/users/3"
router.use((ctx, next) => {
// redirect to named route
ctx.redirect(ctx.router.url('sign-in'));
});
router.url('user', { id: 3 }, { query: { limit: 1 } });
// => "/users/3?limit=1"
router.url('user', { id: 3 }, { query: 'limit=1' });
// => "/users/3?limit=1"router.param(param, middleware) โ `Router`
Run middleware for named route parameters. Useful for auto-loading or
validation.
Kind: instance method of [Router](#exp_module_egg-router--Router)
| Param | Type |
|---|---|
| param | String |
| middleware | function |
Example
router
.param('user', (id, ctx, next) => {
ctx.user = users[id];
if (!ctx.user) return (ctx.status = 404);
return next();
})
.get('/users/:user', (ctx) => {
ctx.body = ctx.user;
})
.get('/users/:user/friends', (ctx) => {
return ctx.user.getFriends().then(function (friends) {
ctx.body = friends;
});
});
// /users/3 => {"id": 3, "name": "Alex"}
// /users/3/friends => [{"id": 4, "name": "TJ"}]Router.url(path, params [, options]) โ `String`
Generate URL from url pattern and given params.
Kind: static method of [Router](#exp_module_egg-router--Router)
| Param | Type | Description |
|---|---|---|
| path | String |
url pattern |
| params | Object |
url parameters |
| [options] | Object |
options parameter |
| [options.query] | Object | String |
query options |
Example
const url = Router.url('/users/:id', { id: 1 });
// => "/users/1"
const url = Router.url('/users/:id', { id: 1 }, { query: { active: true } });
// => "/users/1?active=true"Tests
Run tests using npm test.
Breaking changes on v3
- Drop generator function support
- Drop Node.js < 18.19.0 support
License
Contributors
Made with contributors-img.
File: packages/path-matching/README.md
@eggjs/path-matching
Installation
npm install @eggjs/path-matchingUsage
import { pathMatching } from '@eggjs/path-matching';
const options = {
ignore: '/api', // string will use parsed by path-to-regexp
// support regexp
ignore: /^\/api/,
// support function
ignore: (ctx) => ctx.path.startsWith('/api'),
// support Array
ignore: [(ctx) => ctx.path.startsWith('/api'), /^\/foo$/, '/bar'],
// support match or ignore
match: '/api',
// custom path-to-regexp module, default is `path-to-regexp@6`
// pathToRegexpModule: customPathToRegexp,
};
const match = pathMatching(options);
assert.equal(match({ path: '/api' }), true);
assert.equal(match({ path: '/api/hello' }), true);
assert.equal(match({ path: '/api' }), true);options
match{String | RegExp | Function | Array} - if request path hitoptions.match, will returntrue, otherwise will returnfalse.ignore{String | RegExp | Function | Array} - if request path hitoptions.ignore, will returnfalse, otherwise will returntrue.pathToRegexpModule{Object | Function} - custom path-to-regexp module. Default ispath-to-regexp@6. Supports bothpath-to-regexp@6andpath-to-regexp@8+formats.
ignore and match can not both be presented.
and if neither ignore nor match presented, the new function will always return true.
License
Contributors
Made with contributors-img.
File: packages/loader-fs/README.md
@eggjs/loader-fs
Loader-facing filesystem abstraction for Egg loaders and bundled runtimes.
Usage
import { RealLoaderFS, type LoaderFS } from '@eggjs/loader-fs';
const loaderFS: LoaderFS = new RealLoaderFS();LoaderFS intentionally covers only the operations required by loader code:exists, stat, realpath, readJSON, glob, and loadFile. A source with
an authoritative, precomputed directory view may also implementgetKnownFiles; undefined means discovery should fall back to glob, while
an empty array means the directory is authoritatively empty.
File: packages/koa-static-cache/README.md
eggjs/koa-static-cache
Static cache middleware for koa.
Differences between this library and other libraries such as static:
- There is no directory or
index.htmlsupport. - You may optionally store the data in memory - it streams by default.
- Caches the assets on initialization - you need to restart the process to update the assets.(can turn off with options.preload = false)
- Uses MD5 hash sum as an ETag.
- Uses
.gzfiles if present on disk, like nginx gzip_static module
Forked from https://github.com/koajs/static-cache, refactor with TypeScript to support CommonJS and ESM both.
Installation
npm install @eggjs/koa-static-cacheAPI
staticCache([options])
const path = require('path');
const { staticCache } = require('@eggjs/koa-static-cache');
app.use(
staticCache(path.join(__dirname, 'public'), {
maxAge: 365 * 24 * 60 * 60,
}),
);options.dir(str) - the directory you wish to serve, default toprocess.cwd.options.maxAge(int) - cache control max age for the files,0by default.options.cacheControl(str) - optional cache control header. Overridesoptions.maxAge.options.buffer(bool) - store the files in memory instead of streaming from the filesystem on each request.options.gzip(bool) - when request's accept-encoding include gzip, files will compressed by gzip.options.usePrecompiledGzip(bool) - try use gzip files, loaded from disk, like nginx gzip_staticoptions.alias(obj) - object map of aliases. See below.options.prefix(str) - the url prefix you wish to add, default to''.options.dynamic(bool) - dynamic load file which not cached on initialization.options.filter(function | array) - filter files at init dir, for example - skip non build (source) files. If array set - allow only listed filesoptions.preload(bool) - caches the assets on initialization or not, default totrue. always work together withoptions.dynamic.options.files(obj) - optional files object. See below.
Aliases
For example, if you have this alias object:
const options = {
alias: {
'/favicon.png': '/favicon-32.png',
},
};Then requests to /favicon.png will actually return /favicon-32.png without redirects or anything.
This is particularly important when serving favicons as you don't want to store duplicate images.
Files
You can pass in an optional files object.
This allows you to do two things:
Combining directories into a single middleware
Instead of doing:
app.use(staticCache('/public/js'));
app.use(staticCache('/public/css'));You can do this:
const files = {};
// Mount the middleware
app.use(staticCache('/public/js', {}, files));
// Add additional files
staticCache('/public/css', {}, files);The benefit is that you'll have one less function added to the stack as well as doing one hash lookup instead of two.
Editing the files object
For example, if you want to change the max age of /package.json, you can do the following:
const files = {};
app.use(
staticCache(
'/public',
{
maxAge: 60 * 60 * 24 * 365,
},
files,
),
);
files['/package.json'].maxAge = 60 * 60 * 24 * 30;Using a LRU cache to avoid OOM when dynamic mode enabled
You can pass in a lru cache instance which has tow methods: get(key) and set(key, value).
const LRU = require('lru-cache');
const files = new LRU({ max: 1000 });
app.use(
staticCache({
dir: '/public',
dynamic: true,
files,
}),
);License
Contributors
Made with contributors-img.