@maiyunnet/kebab
v9.29.0
Published
Simple, easy-to-use, and fully-featured Node.js framework that is ready-to-use out of the box.
Readme
A simple, easy-to-use, full-featured Node.js framework, ready to use out of the box.
Project statement: This project was first committed to GitHub on February 25, 2019. Its predecessor, the Mutton framework, was first committed on July 13, 2015, after several years of development. In 2026, the project formally introduced AI-assisted development and adaptations for AI integration, but software engineers still lead, review, verify and test the project; it is not automatically generated by simply telling AI what we want. Non-Chinese content is translated with assistance from Byterun Translator, so the wording may have an AI-like tone.
The releases on GitHub may be incomplete. For versions after 2.0.0, refer to NPM as the source of record: NPM
Languages
Requirements
Node.js 24+
Installation
Run npm i @maiyunnet/kebab to install the latest version. After installation, run npx kebab init to initialize, then run npx kebab to start the project.
You can also start the project with pm2 by running pm2 start npx --name "kebab" -- kebab.
Libraries
Ai, Buffer, Captcha, Consistent, Cookie, Core, Cron, Crypto, Db (MySQL, PostgreSQL), Dns (DNSPod, Alibaba Cloud), Fs, Kv (Redis), Lan, Lang, Net, Ratelimit, S3, Scan, Session, Socket, Sql, Ssh (Shell, Sftp), Text, Time, Turnstile (Cloudflare, Tencent Cloud), Undici, Vector (Milvus), Ws, Zip, Zlib.
Use Undici for new HTTP requests; Net remains available for compatibility with existing code.
Selected features
Ready to use
Following the principle of being ready to use out of the box, Kebab wraps commonly used libraries in a consistent style.
Hot updates
Call lCore.sendRestart() to initiate a graceful restart: new processes accept connections, while old processes exit once their existing connections have ended.
Multiple processes
Create multiple processes based on the number of CPU cores to make full use of a multi-core CPU.
Shared connection pools
Different sites in the same process share a connection pool when connecting to the same Db, Kv or other server, maximizing efficiency and reducing overhead.
Easy-to-use Undici library
import * as lUndici from '@maiyunnet/kebab/lib/undici.js';
const res = await lUndici.open('https://example.com/test').post().data({ 'a': '1', 'b': '2' }).request();
const content = await res.getContent();You can also use it like this:
const res = await lUndici.get('https://example.com/test');
const content = await res.getContent();When you only need a JSON response, use the corresponding shortcut method. Request or read failures return null; JSON parsing failures return false:
const data = await lUndici.getResponseJson('https://example.com/data');You can customize hostname resolution. Here, hostsMap is a caller-provided mapping of hostnames to IP addresses:
const res = await lUndici.get('https://example.com/test', {
'hosts': hostsMap,
});
const content = await res.getContent();You can also use another local network interface. Here, localAddress is the local IP address from your configuration:
const res = await lUndici.get('https://example.com/test', {
'local': localAddress,
});
const content = await res.getContent();It also has a complete cookie manager that stores received cookies in a caller-provided object. When sending requests, the system selects cookies based on their domain, path and other attributes, and discards Set-Cookie values with invalid cross-domain settings:
import * as lCookie from '@maiyunnet/kebab/lib/cookie.js';
const cookie: Record<string, lCookie.ICookie> = {};
const res1 = await lUndici.get('https://example.com/login', { 'cookie': cookie });
await res1.getContent();
const res2 = await lUndici.get('https://example.com/profile', { 'cookie': cookie });
await res2.getContent();Note: Undici supports both options and chained open calls, such as
await lUndici.open('https://example.com/file').follow().timeout(60).save(filePath).request();filePathis a caller-provided local file path.
Easy-to-use Db library
A wide range of useful interfaces makes it easy to select the data you need from a database. In the following controller method snippets, db is a database connection pool, and mOrder and mUser are application-defined models:
const query = mOrder.where<mOrder>(db, {
'state': '1',
}, { 'ctr': this }).by('id', 'DESC').page(10, 1);
const list = await query.all();
if (list === false) {
return [0, 'Query failed.'];
}
const count = list.length;
const total = await query.total();Get a user:
const user = await mUser.select<mUser>(db, ['id', 'user'], { 'ctr': this }).filter([
['time_add', '>=', '1583405134']
]).first();XSRF checks
For data-changing endpoints that rely on browser session cookies, use _valibotx to validate input and check XSRF, preventing cross-site request forgery. Pages generate a token with _enabledXsrf() and submit it as _xsrf; read-only endpoints use _valibot.
QR code login
The Scan library makes QR code login easy to implement.
Reverse proxy
Use the Undici library’s rproxy method with the current controller and a route-prefix mapping to implement a reverse proxy.
More features await your exploration
Code examples
Generate a random 16-digit string
import * as lCore from '@maiyunnet/kebab/lib/core.js';
const str = lCore.random(16, lCore.RANDOM_N);Generate a CAPTCHA image
import * as lCaptcha from '@maiyunnet/kebab/lib/captcha.js';
const captcha = lCaptcha.get(400, 100);
const svg = captcha.getBuffer();
const phrase = captcha.getPhrase();getBuffer() returns an SVG string; phrase is the corresponding CAPTCHA text, which should be stored on the server for subsequent verification.
Get a list
const userList = await mUser.where<mUser>(db, [
['state', '!=', '0'],
{
'type': ['1', '2', '3'],
'is_lock': '0',
},
], { 'ctr': this }).all();Note: Values in query conditions are passed through parameter binding. Handwritten SQL should also bind user input, and dynamic table and column names should be restricted to trusted values.
Automatic table prefixes and identifier quoting with Sql
import * as lSql from '@maiyunnet/kebab/lib/sql.js';
const sql = lSql.get({ 'service': lSql.ESERVICE.MYSQL, 'pre': 'test' });
sql.select(['SUM(user.age) age'], 'order').leftJoin('user', { 'order.user_id': lSql.column('user.id') });
const statement = sql.getSql();The following output is for MySQL with the test_ table prefix; PostgreSQL uses schemas and double quotes:
SELECT SUM(`test_user`.`age`) AS `age` FROM `test_order` LEFT JOIN `test_user` ON `test_order`.`user_id` = `test_user`.`id`Localization
In the following controller method snippet, lang is a validated language code:
if (!await this._loadLocale(lang, 'test')) {
return 'Could not load locale.';
}
return this._l('copy');Depending on the value of lang, the output will be Copy, 复制, 複製, コピー, etc., configured in the site’s /data/locale/ directory.
Data validation
New endpoints use Valibot to validate strings, numbers, value ranges and regular expressions. On success, use output to obtain the converted data. Define rules in an application module for reuse, such as lib/schema/input.ts:
import * as lCore from '@maiyunnet/kebab/lib/core.js';
const v = lCore.v;
export const inputSchema = v.strictObject({
'he': v.pipe(v.string(), v.nonEmpty()),
'num': v.pipe(v.string(), v.toNumber(), v.gtValue(10)),
'reg': v.pipe(v.string(), v.regex(/^[A-CX-Z5-7]+$/)),
'arr': v.picklist(['a', 'x', 'hehe']),
});Import the rules in the ctr/main.ts controller and validate within a method:
import * as lInputSchema from '../lib/schema/input.js';
const parsed = this._valibot(lInputSchema.inputSchema, this._post);
if (!parsed.success) {
return parsed.response;
}
return [1, parsed.output];Use _valibotx instead for data-changing endpoints that rely on browser session cookies. See the quick start for multilingual messages and XSRF usage, and /test/ctr-valibot for a working example.
Other examples
Visit /test/ to see more examples.
License
This framework is licensed under Apache-2.0.
