Koatty is an agile development framework based on Koa2, featuring automatic dependency injection (IoC) and Aspect-Oriented Programming (AOP) using TypeScript decorators, similar to SpringBoot.
-
Install the Command Line Tool
npm i -g koatty_cli
The version of the command line tool corresponds to the version of the Koatty framework. For example,
koatty_cli@1.11.x
supports the new features ofkoatty@1.11.x
. -
Create a New Project
kt new projectName cd ./projectName yarn install
-
Start the Service
-
Development Mode
npm run dev
-
Production Mode
npm start
Access the application in your browser at
http://localhost:3000
.
-
It is highly recommended to use Visual Studio Code (VSCode) for development. Edit the .vscode/launch.json
file in the project directory (you can also open it by clicking on Debug > Add Configuration):
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "TS Program",
"args": [
"${workspaceRoot}/src/App.ts"
],
"runtimeArgs": [
"--nolazy",
"-r",
"ts-node/register"
],
"sourceMaps": true,
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"internalConsoleOptions": "neverOpen"
}
]
}
Select TS Program
to start in debug mode and access http://127.0.0.1:3000
.
Koatty currently only supports Jest for writing test cases.
import request from 'supertest';
import { ExecBootStrap } from 'koatty';
import { App } from '../src/App';
describe('UT example', () => {
let server;
beforeAll(async () => {
const appInstance = await ExecBootStrap()(App);
server = appInstance.callback();
});
it('request', async (done) => {
const rsp = await request(server).get('/path/to/server');
expect(rsp.status).toBe(200);
done();
});
});
The Koatty CLI koatty_cli
creates the following directory structure by default when creating a project:
<projectName>
├── .vscode # VSCode configuration
│ └── launch.json # Node local debugging script
├── dist # Compiled directory
├── src # Project source code
│ ├── config
│ │ ├── config.ts # Framework configuration
│ │ ├── db.ts # Database configuration
│ │ ├── middleware.ts # Middleware configuration
│ │ ├── plugin.ts # Plugin configuration
│ │ └── router.ts # Router configuration
│ ├── aspect # AOP aspect classes
│ │ └── TestAspect.ts
│ ├── controller # Controllers
│ │ └── TestController.ts
│ ├── middleware # Middleware
│ │ ├── JwtMiddleware.ts
│ │ └── ViewMiddleware.ts
│ ├── model # Persistence layer
│ │ └── TestModel.ts
│ ├── plugin # Plugins
│ │ └── TestPlugin.ts
│ ├── proto # Protocol Buffers
│ │ └── test.proto
│ ├── resource # Static data or whitelist
│ │ └── data.json
│ ├── service # Service logic
│ │ └── TestService.ts
│ ├── utils # Utility functions
│ │ └── tool.ts
│ └── App.ts # Entry file
├── static # Static files directory
│ └── index.html
├── test # Test cases
│ └── index.test.js
├── apidoc.json
├── pm2.json
├── package.json
├── README.md
└── tsconfig.json
However, Koatty supports flexible customization of the project structure. Except for the configuration directory (which can be customized through @ConfigurationScan()
), and the static resources directory (which requires modifying the default configuration of the Static middleware), other directory names and structures can be customized.
The default entry file for Koatty is App.ts
, which looks like this:
import { Koatty, Bootstrap } from "koatty";
// import * as path from "path";
@Bootstrap(
// bootstrap function
// (app: any) => {
// Adjust libuv thread pool size
// process.env.UV_THREADPOOL_SIZE = "128";
// Ignore https self-signed verification
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
// }
)
// @ComponentScan('./')
// @ConfigurationScan('./config')
export class App extends Koatty {
public init() {
// this.appDebug = true; // Set debug mode to false in production environment
}
}
The App
class inherits from Koa.Application
. Therefore, an instance of App
is an extended instance of koa
(extended). Koatty defines the project entry through the @Bootstrap()
decorator. The @Bootstrap()
decorator can accept a function as a parameter, which is executed after initializing environmental parameters when the project is loaded.
Koatty uses the @ComponentScan()
decorator to define the project directory. If the project directory is modified, the relative directory name of the project needs to be passed; if certain directories are excluded from automatic loading of beans, the files that should not be automatically loaded can be placed outside the project directory.
Koatty uses the @ConfigurationScan()
decorator to customize the project configuration directory. The default value is ./config
, which is the config
subdirectory under the project directory.
App
is a global application object. Only one instance will be instantiated in an application, inheriting from Koa.Application
. We can mount some global methods and objects on the App
object. It is easy to extend the App
object in plugins or applications.
In CONTROLLER
, SERVICE
, COMPONENT
type beans, the App
object is injected by default and can be used directly:
@Controller()
export class TestController {
...
test() {
// Print app object
console.log(this.app);
}
}
In MIDDLEWARE
type beans, the App
object is passed as a function argument:
@Middleware()
export class TestMiddleware {
run(options: any, app: Koatty) {
...
// Print app object
console.log(app);
}
}
Ctx
is a request-level object, inheriting from Koa.Context
. Every time a user request is received, the framework instantiates a Ctx
object, which encapsulates the information of this user request and provides many convenient methods to get request parameters or set response information.
In CONTROLLER
type beans, the Ctx
object is a member property and can be used directly:
@Controller()
export class TestController {
...
test() {
// Print ctx object
console.log(this.ctx);
}
}
In MIDDLEWARE
type beans, the Ctx
object is passed as an argument to the middleware execution function:
@Middleware()
export class TestMiddleware {
run(options: any, app: Koatty) {
...
return async function (ctx: any, next: any) {
// Print ctx object
console.log(ctx);
return next();
};
}
}
In SERVICE
, COMPONENT
type beans, the Ctx
object needs to be passed manually:
@Service()
export class RequestService {
app: App;
Test(ctx: KoattyContext) {
// Print ctx object
console.log(ctx);
}
}
Note the difference between app.context
and context.app
: app.context
is a prototype for the context object created for each request. Each time a request is received, Koa creates a new context object for that request and assigns it to the current ctx
variable. Although each request's context is based on app.context
, this does not mean that app.context
will be overwritten. app.context
is actually a template for generating new context instances. The context contains a reference to app
, but the relationship between them is unidirectional (the context accesses app
through properties, but not vice versa).
In actual projects, various configurations are definitely needed, including framework-required configurations and project-customized configurations. Koatty manages all configurations uniformly and divides them into different configuration files according to different functions.
config.ts
: General configurationsdb.ts
: Database configurationsrouter.ts
: Router configurationsmiddleware.ts
: Middleware configurationsplugin.ts
: Plugin configurations
In addition to the common configuration files mentioned above, Koatty also supports custom configuration file naming.
Configuration files are placed in the src/config/
directory by default. You can also customize the configuration scan path in the entry file App.ts
:
@ConfigurationScan('./myconfig')
export class App extends Koatty {
public init() {
...
}
}
When Koatty starts, it will automatically scan all .ts
files in the project src/myconfig
directory and load them as configurations according to the filename.
Koatty's configuration files must be exported in standard ES6 Module format, otherwise they will not be loaded. The format is as follows:
export default {
...
aa: "bb",
cc: {
dd: ""
}
...
}
There are two ways to read configurations conveniently in the project: Method One (using app.config
function):
...
const conf: Test = this.app.config("test");
Method Two (using decorator injection, recommended usage):
@Controller()
export class AdminController {
@Config("test")
conf: Test;
}
The configuration type Test
in the above configuration reading code is a defined configuration class. Of course, you can also use Object
or any
types.
When Koatty scans the configuration file directory at startup, it classifies configurations according to filenames. For example, after loading db.ts
, the configuration items in the file need to have a type added:
// The second parameter of the config function is the configuration type
const conf: Test = this.app.config("test", "db");
Or
@Config("test", "db")
conf: Test;
The default classification of configurations is config
, so configuration items in config.ts
do not need to fill in the type parameter.
Koatty supports configuration hierarchy when reading configurations. For example, in the configuration file db.ts
:
export default {
/* database config */
database: {
db_type: 'mysql', // support postgresql, mysql...
db_host: '127.0.0.1',
db_port: 3306,
db_name: 'test',
db_user: 'test',
db_pwd: '',
db_prefix: '',
db_charset: 'utf8'
}
}
To read the value of db_host
:
@Config("database.db_host", "db")
dbHost: string;
Or
const dbHost: string = this.app.config("database.db_host", "db");
It should be noted that hierarchical configurations only support direct access to the second level. For deeper levels, assign the value to a variable and retrieve it again:
// config
export default {
test: {
bb: {
cc: 1
}
}
}
const conf: any = this.app.config("test");
const cc: number = conf.bb.cc;
Koatty can automatically recognize the current runtime environment and load corresponding configurations (if available) according to the runtime environment.
The runtime environment is defined by three properties:
-
appDebug
: Defined in the constructor method (init
) of the project entry file.@Bootstrap() export class App extends Koatty { public init() { // appDebug is true for development mode // appDebug is false for production mode this.appDebug = false; } }
-
process.env.NODE_ENV
: The runtime environment variable of Node.js, which can be defined in the system environment or in the startup function of the project entry file. -
process.env.KOATTY_ENV
: The runtime environment variable of the Koatty framework.
The relationship and difference between the three variables:
Variable | Value | Description | Priority |
---|---|---|---|
appDebug |
true/false |
Debug mode | High |
process.env.KOATTY_ENV |
development/production |
Framework runtime environment variable | Medium |
process.env.NODE_ENV |
development/production |
Node.js runtime environment variable | Low |
The priority here refers to the priority of loading runtime configurations. Higher priority configurations will override lower priority configurations.
app.env = process.env.KOATTY_ENV || process.env.NODE_ENV;
If app.env = production
, koatty_config
will automatically load configuration files with _pro.ts
or _production.ts
suffixes. If app.env = development
, it will automatically load configuration files with _dev.ts
or _development.ts
suffixes.
For example:
# Automatically loads config_dev.ts or config_development.ts
NODE_ENV=dev ts-node "test/test.ts"
Through flexible configuration of these three variables, diverse runtime environments and configurations can be supported.
Koatty can automatically recognize command line arguments and automatically fill them into corresponding configuration items:
# Automatically fills the value of config.cc.dd.ee
NODE_ENV=dev ts-node "test/test.ts" --config.cc.dd.ee=77
Koatty can automatically replace configuration items in configuration files identified by ${}
placeholders with values of the same name in process.env
:
// Automatically fills the value of ff_value
export default {
...
ff: "${ff_value}"
...
}
NODE_ENV=dev ff_value=999 ts-node "test/test.ts"
process.env.ROOT_PATH
: The root directory defined by Koatty. Can be used anywhere in the project.process.env.APP_PATH
: The application directory defined by Koatty (in debug mode, it is/projectDIR/src
; in production mode, it is/projectDIR/dist
). Can be used anywhere in the project.process.env.KOATTY_PATH
: The root directory of the Koatty framework (/projectDIR/node_modules/koatty/
). Can be used anywhere in the project.process.env.LOGS_PATH
: The directory for saving logs (default is/projectDIR/logs
, which can be modified in the configuration). Can be used anywhere in the project.
Koatty encapsulates a routing library koatty_router
specifically for handling routes, supporting HTTP1/2, WebSocket, gRPC, and other protocol types.
The @Controller()
decorator's parameter serves as the controller's access entry, with the default value being /
. Then, iterate over the controller's methods and register route mappings using decorators such as GetMapping
, DeleteMapping
, PutMapping
, PostMapping
.
For example:
@Controller("/admin")
export class AdminController {
...
@GetMapping("/test")
test() {
...
}
...
}
The above code registers the route /admin/test
to AdminController.test()
.
Note: In gRPC services, the route bound by @Controller
must match the serviceName
defined in the proto.
For example, @Controller("/Book")
binds to the service Book
in the proto.
Used to bind routes to controller methods. Refer to the decorator section for details.
The method routing decorators are @GetMapping
, @PostMapping
, @DeleteMapping
, @PutMapping
, @PatchMapping
, @OptionsMapping
, @HeadMapping
, @RequestMapping
.
Note: In gRPC services, please use @PostMapping
or @RequestMapping
for binding, and the path
in @RequestMapping
must match the method name defined in the proto; in WebSocket services, please use @GetMapping
or @RequestMapping
for binding.
In method routing, there is a special parameter route that can easily implement RESTful APIs.
@Controller("/admin")
export class AdminController {
...
@GetMapping("/test/:id") // Declare parameters in the method decorator
test(@PathVariable("id") id: number) { // Use PathVariable to get the bound parameter
...
}
...
}
Koatty's routing component koatty_router
is based on @koa/router
(except for gRPC), and detailed routing tutorials can be found in @koa/router
.
The custom route configuration is stored in src/config/router.ts
, which initializes the routing instance.
export default {
prefix: string;
methods?: string[];
routerPath?: string;
sensitive?: boolean;
strict?: boolean;
};
// If the project protocol is grpc, the proto file path needs to be defined:
export default {
// prefix: string;
// methods?: string[];
// routerPath?: string;
// sensitive?: boolean;
// strict?: boolean;
ext: {
protoFile: "", // gRPC proto file
};
};
- The
@Controller()
decorator has two roles: declaring the type of the bean as a controller and binding the controller route. If no path is specified when using the@Controller()
decorator (no parameters), the default value is/
. - Method routing decorators can be added multiple times to the same method. However, the
@Controller()
decorator can only be used once per class. - If duplicate routes are bound, the first loaded route rule takes effect according to the loading order of the controller class in the IOC container. This issue needs attention. In future versions, priority features may be added to control this.
Routes support regular expressions and parameter binding (not available in gRPC services). Detailed routing tutorials can be found in @koa/router
.
Koatty is built on top of Koa, so the form of middleware in Koatty is essentially the same as in Koa, which is based on the onion model. Every time we write a middleware, it is like wrapping another layer around the onion.
Koatty's framework defaults to loading middleware such as trace and payload, which can meet most web application scenarios. Users can also add their own middleware for extension.
Different from Koa middleware, Koatty middleware is written in the form of classes and uses the @Middleware
decorator to declare the component type.
Middleware classes must contain a method named run(options: any, app: App)
. This method is called when the application starts and returns a function (ctx: any, next: any){}
, which is the format of the Koa middleware.
Use the command line tool koatty_cli
to execute commands in the command line:
# Create a custom middleware named jwt
kt middleware jwt
This will automatically generate a file src/middleware/JwtMiddleware.ts
in the project directory with the generated middleware code template:
/**
* Middleware
* @return
*/
import { Middleware, Helper } from "koatty";
import { App } from '../App';
@Middleware()
export class JwtMiddleware {
run(options: any, app: App) {
// Logic before returning middleware, e.g., reading configuration, etc.
...
return function (ctx: any, next: any) {
// Implement middleware logic here
...
}
}
}
Modify the project middleware configuration in src/config/middleware.ts
:
list: ['JwtMiddleware'], // List of loaded middleware
config: { // Middleware configuration
JwtMiddleware: {
// Middleware configuration items
}
}
To disable middleware developed by the project, simply modify the middleware configuration file:
list: [], // If PassportMiddleware is not in the list, the Passport middleware will not execute
config: { // Middleware configuration
'PassportMiddleware': {...},
}
Koatty supports using Koa middleware (including middleware for Koa 1.x and 2.x):
const passport = require('koa-passport');
@Middleware()
export class PassportMiddleware {
run(options: any, app: App) {
return passport.initialize();
}
}
Mount and configure usage:
list: ['PassportMiddleware'], // List of loaded middleware
config: { // Middleware configuration
'PassportMiddleware': {
// Middleware configuration items
}
}
Koatty is compatible with Express middleware, and the usage is the same as Koa middleware. The framework will automatically recognize and convert for compatibility.
If the project uses protocols such as grpc
, ws
, wss
, etc., middleware needs to be aware that some properties of ctx
will be inconsistent. For example, ctx.header
does not exist in grpc
. The specific available properties will be explained in the gRPC and WebSocket chapters.
Koatty controller classes use the @Controller()
decorator to declare them, and the parameter of this decorator is used to bind the controller's access route, with the default value being /
. Controller classes are placed in the project's src/controller
folder by default and support subfolders for classification. Koatty controller classes must implement the IController
interface.
Use the koatty_cli
command line tool:
-
Single module mode:
kt controller index # Default http protocol kt controller -t http index kt controller -t grpc index kt controller -t ws index
This will automatically create
src/controller/IndexController.ts
. -
Multi-module mode:
kt controller admin/index
This will automatically create
src/controller/Admin/IndexController.ts
.
The template code for the controller is as follows:
import { Controller, GetMapping } from "koatty";
import { App } from '../../App';
@Controller("/")
export class IndexController {
app: App;
ctx: KoattyContext;
/**
* constructor
*
*/
constructor(ctx: KoattyContext) {
this.ctx = ctx;
}
@GetMapping("/")
index() {
return this.ok('Hello, Koatty!');
}
}
Controller classes must implement the IController
interface.
The constructor method of the controller class must have ctx: KoattyContext
as the first parameter and assign it to the ctx
property in the constructor method:
constructor(ctx: KoattyContext) {
this.ctx = ctx;
}
According to the software layered architecture, controllers should not be called by other controllers (if indeed needed, move the logic to the Service layer for code reuse), nor should they be referenced by other components (anti-pattern).
Koatty parses and processes request parameters, and in the controller, we can obtain parameter values through the following methods:
-
Using
@Get
decorator:... @GetMapping("/get") async get(@Get("id") id: number): Promise<any> { console.log(id); } ...
-
Using
@RequestParam
decorator:... @GetMapping("/get") async get(@RequestParam("id") id: number): Promise<any> { console.log(id); } ...
-
Using
ctx.query
:... @GetMapping("/get") async get(): Promise<any> { console.log(this.ctx.query["id"]); } ...
-
Using
@PathVariable
decorator:... @GetMapping("/test/:id") // Declare parameters in the method decorator test(@PathVariable("id") id: number) { // Use PathVariable to get the bound parameter ... } ...
-
Using
ctx.requestParam
:... @GetMapping("/test/:id") // Declare parameters in the method decorator test() { // Use PathVariable to get the bound parameter console.log(this.ctx.requestParam["id"]); } ...
-
Using
@Post
decorator:... @PostMapping("/post") async post(@Post("id") id: number): Promise<any> { console.log(id); } ...
-
Using
@RequestBody
decorator:... @PostMapping("/post") async post(@RequestBody() body: any): Promise<any> { console.log(body.post); } ...
-
Using
ctx.requestBody
:... @PostMapping("/post") async post(): Promise<any> { console.log(ctx.requestBody.post); } ...
-
Using
@File
decorator:... @PostMapping("/post") async post(@File("filename") fileObject: any): Promise<any> { console.log(fileObject); } ...
-
Using
@RequestBody
decorator:... @PostMapping("/post") async post(@RequestBody() body: any): Promise<any> { console.log(body.file); } ...
-
Using
ctx.requestBody
:... @PostMapping("/post") async post(): Promise<any> { console.log(ctx.requestBody.file); } ...
-
Using
@Header
decorator:... @PostMapping("/get") async get(@Header("x-access-token") token: string): Promise<any> { console.log(token); } ...
-
Using
ctx.get
:... @PostMapping("/get") async get(): Promise<any> { const token = this.ctx.get("x-access-token"); console.log(token); } ...
-
Using
ctx.header
:... @PostMapping("/get") async get(): Promise<any> { console.log(this.ctx.header); } ...
Class references follow TypeScript's scope private | protected | public
. If not explicitly declared, the scope of class methods is public
.
As long as a controller class method is bound to a route, the method can be accessed via URL mapping (even if the method's scope is not public
). This is because currently, it is not possible to obtain the method's scope keyword through reflection (if you know how, please let me know), and URL access scope control has not been implemented.
Refer to the BaseController API
for controller properties and methods.
In simple terms, a Service is an abstraction layer used for encapsulating business logic in complex business scenarios. The benefits of providing this abstraction are:
- Keeping the logic in the Controller simpler.
- Maintaining the independence of business logic, abstracted Services can be called repeatedly by multiple Controllers.
- Separating logic from presentation, making it easier to write test cases.
Koatty service classes use the @Service()
decorator to declare them. Service classes are placed in the project's src/service
folder by default and support subfolders for classification. Koatty service classes must implement the IService
interface.
Use the koatty_cli
command line tool:
kt service test
This will automatically create src/service/test.js
, with the generated template code:
import { Service, Autowired, Scheduled, Cacheable } from "koatty";
import { App } from '../App';
@Service()
export class TestService {
app: App;
// Implement test method
test(name: string) {
return name;
}
}
Inject through decorators:
@Autowired()
testService: TestService;
Get through the IOC container:
this.testService = IOCContainer.get("TestService", "SERVICE");
Call the service class method:
this.testService.test();
The persistence layer is responsible for persisting business objects from the service layer into the database. ORM encapsulates database access operations, directly mapping objects to the database.
The persistence layer is a type of business logic layering and is not mandatory in the framework. The persistence layer is of type COMPONENT
in the framework's IOC container. It is loaded together with plugins when the framework starts. Plugins can reference the persistence layer.
Koatty currently supports TypeORM by default. If you need to use other types of ORMs, such as Sequelize or Mongoose, you can refer to the koatty_typeorm
plugin to implement it yourself.
Create data models and entities using the koatty_cli
command line tool:
kt model test
This tool will automatically create an entity class UserEntity
and a model class UserModel
:
@Component()
@Entity('user') // Corresponds to the database table name
export class UserEntity extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@CreateDateColumn()
createdDate: Date;
@UpdateDateColumn()
updatedDate: Date;
}
The model class UserModel
has already generated common CURD data operation methods, including pagination.
In addition, the koatty_typeorm
plugin will be automatically introduced in the plugin
directory, which needs to be loaded in the plugin list.
Inject through decorators:
@Autowired()
userModel: UserModel;
Get through the IOC container:
this.userModel = IOCContainer.get("UserModel", "COMPONENT");
Call the model class method:
this.userModel.Find();
Modify the database-related configuration items in the project's plugin
configuration config/plugin.ts
:
// src/config/plugin.ts
export default {
list: ['TypeormPlugin'], // List of loaded plugins, execution order according to array element order
config: { // Plugin configuration
TypeormPlugin: {
// Default configuration items
"type": "mysql", // mysql, mariadb, postgres, sqlite, mssql, oracle, mongodb,
host: "127.0.0.1",
port: 3306,
username: "test",
password: "test",
database: "test",
"synchronize": false, // true means entities will synchronize with the database every time the application runs
"logging": true,
"entities": [`${process.env.APP_PATH}/model/*`],
"entityPrefix": ""
},
},
};
For easier management, we can also unify the database configuration to config/db.ts
(you need to delete the TypeormPlugin
configuration in config/plugin.ts
):
export default {
/* database config */
"DataBase": { // used koatty_typeorm
// Default configuration items
"type": "mysql", // mysql, mariadb, postgres, sqlite, mssql, oracle, mongodb,
host: "${mysql_host}",
port: "${mysql_port}",
username: "${mysql_user}",
password: "${mysql_pass}",
database: "${mysql_database}",
"synchronize": false, // true means entities will synchronize with the database every time the application runs
"logging": true,
"entities": [`${process.env.APP_PATH}/model/*`],
"entityPrefix": ""
},
"CacheStore": {
type: "memory", // redis or memory
// key_prefix: "koatty",
// host: '127.0.0.1',
// port: 6379,
// name: "",
// username: "",
// password: "",
// db: 0,
// timeout: 30,
// pool_size: 10,
// conn_timeout: 30
},
};
The plugin mechanism is to manage and orchestrate relatively independent business logic on the premise of ensuring that the core of the framework is sufficiently lean and stable.
In the process of using middleware, we found some problems:
- The positioning of middleware is to intercept user requests and do something before and after them, such as authentication, security checks, access logs, etc. However, in reality, some functions are unrelated to requests, such as scheduled tasks, message subscriptions, background logic, etc.
- Some functions contain very complex initialization logic that needs to be completed when the application starts. This is obviously not suitable for implementation in middleware.
In summary, we need a more powerful mechanism to manage and orchestrate those relatively independent business logics. Typical application scenarios include service registration discovery, pulling configurations from the configuration center, etc.
In the framework's IOC container, plugins are a special type of COMPONENT
.
Plugins should try to maintain independence and not couple with other components. If necessary, plugins can call the persistence layer (operate databases and caches, etc.). However, they cannot call the service layer, middleware, or controllers, nor can they be called by other components.
Plugins are generally reused through npm modules:
npm i koatty_apollo --save
Use koatty_cli
to create a plugin class in the application:
kt plugin apollo
This will generate the plugin code template:
import { Plugin, IPlugin } from 'koatty';
import { App } from '../App';
import { Apollo } from 'koatty_apollo';
@Plugin()
export class ApolloPlugin {
run(options: any, app: App) {
return Apollo(options, app);
}
}
Then declare it in the application's config/plugin.ts
:
list: ['ApolloPlugin'], // List of loaded plugins
config: { // Plugin configuration
'ApolloPlugin': {
// Plugin configuration items
}
}
To disable a plugin in the project, simply modify the plugin configuration file:
list: [], // If ApolloPlugin is not in the list, the ApolloPlugin plugin will not execute
config: { // Plugin configuration
'ApolloPlugin': {...},
}
Parameter validation is a very common function in projects, and Koatty has specially encapsulated a library koatty_validation
, which can be easily used in projects. Koatty provides two schemes for parameter validation, suitable for different scenarios:
-
@Valid
and@Validated
decorators are only applicable to controller classes.@RequestMapping('/') // Check if the input parameter is an email index(@RequestBody() @Valid("IsEmail") body: string): Promise<any> { return this.ok('Hi Koatty'); }
-
The
@Validated
decorator needs to be used with a DTO class:@RequestMapping('/SayHello') @Validated() // DTO parameter validation decorator SayHello(@RequestBody() params: SayHelloRequestDto): Promise<SayHelloReplyDto> { const res = new SayHelloReplyDto(); return Promise.resolve(res); }
Use the cli tool to create a DTO class:
kt dto SayHelloRequest
Add validation rules to the DTO class:
@Component()
export class SayHelloRequestDto {
@IsNotEmpty({ message: "Phone number cannot be empty" })
phoneNum: string;
...
}
For bean parameter validation in non-controller types, we can use FunctionValidator
and ClassValidator
.
-
FunctionValidator
:// Throw an error directly FunctionValidator.IsNotEmpty(str, "cannot be empty"); FunctionValidator.Contains(str, {message: "must contain s", value: "s"}); // Return true or false if (ValidFuncs.IsEmail(str)) { ... }
-
ClassValidator
:class SchemaClass { @IsDefined id: number; @IsNotEmpty name: string; } const ins = new SchemaClass(); ins.name = ""; ClassValidator.valid(SchemaClass, ins, true).catch(err => { console.log(err); })
koatty_validation
defines a series of common validation rules.
In addition to built-in rules, custom function validation can also be defined:
Use custom functions with the @Valid
decorator:
@Controller('/api/login')
export class LoginController {
...
@Validated()
async GetSignout(@Post() someObj: ObjectDto) {
// do something
}
...
}
// class ObjectDto
export class ObjectDto {
...
@CheckFunc((value: unknown) => {
return value !== undefined && value !== null;
}, { message: "Username cannot be empty" })
username: string;
...
}
-
Custom validation in DTO classes:
@Controller('/api/login') export class LoginController { ... @Validated() async GetSignout(@Post() someObj: ObjectDto) { // Call valid() if (!someObj.validUserName()) { throw new Exception("User is disabled", 1004, 200); } } ... }
// class ObjectDto
export class ObjectDto { ... @IsDefined() username: string; validUserName(): boolean { return this.username === "test"; } ... }
Koatty framework encapsulates the koatty_exception
component for handling errors that need to be thrown in the project, supporting the customization of exception classes to handle different business exceptions.
Customize HTTP status, business error codes, and error messages. Save the error stack in logs.
If no custom exception handling is defined in the application, exceptions thrown during program execution will be intercepted and handled uniformly by the framework's default interception mechanism. For example, throwing Error
, the framework can still intercept it.
// res: {"code":1,"message":"error"}
throw new Error("error");
// res: {"code":1000,"message":"error"}
throw new Exception("error", 1000);
// res: {"code":1000,"message":"error"}
ctx.throw("error", 1000);
We can customize exception handling classes, which need to inherit from the Exception
base class:
@ExceptionHandler() // Register global exception handling
export class BusinessException1 extends Exception {
// Handle exceptions uniformly in the handler
async handler(ctx: KoattyContext): Promise<any> {
// Return ctx.res.end for http protocol, handle based on ctx.protocol for gRPC protocol
return ctx.res.end(this.message);
}
}
export class BusinessException2 extends Exception {
// Handle exceptions uniformly in the handler
async handler(ctx: KoattyContext): Promise<any> {
// Return ctx.res.end for http protocol, handle based on ctx.protocol for gRPC protocol
return ctx.res.end({code: this.code, message: this.message});
}
}
In the application code, we can throw different exceptions according to business logic:
// res: {"code":1,"message":"error"}
throw new BusinessException1("error");
// res: {"code":1000,"message":"error"}
throw new BusinessException2("error", 1000);
Koatty provides a decorator @ExceptionHandler()
to register global exception handling.
@ExceptionHandler() // Register global exception handling
export class BusinessException extends Exception {
// Handle exceptions uniformly in the handler
async handler(ctx: KoattyContext): Promise<any> {
// Return ctx.res.end for http protocol, handle based on ctx.protocol for gRPC protocol
return ctx.res.end(this.message);
}
}
Global exception handling is registered only once, and multiple registrations will overwrite each other. After registering global exception handling, unless a different type of exception is explicitly thrown, all exceptions will be intercepted by the global exception handling class.
...
async index(type: string) {
if (type == '1') {
// Specify BusinessException2 to handle exceptions
// res: {"code":1000,"message":"error"}
throw new BusinessException2("error", 1000);
} else {
// Not explicitly specified, handled by global exception handling
// res: error
throw new Error("error", 1000);
}
}
...
Koatty encapsulates a caching library koatty_cacheable
, which supports memory and Redis storage. koatty_cacheable
provides two decorators CacheAble
and CacheEvict
.
Cache configuration is saved in config/db.ts
:
export default {
...
"CacheStore": {
type: "memory", // redis or memory, memory is default
// key_prefix: "koatty",
// host: '127.0.0.1',
// port: 6379,
// name: "",
// username: "",
// password: "",
// db: 0,
// timeout: 30,
// pool_size: 10,
// conn_timeout: 30
},
...
};
The default uses memory storage, and if Redis is needed, Redis connection-related configuration items need to be supplemented.
@CacheAble(cacheName: string, {params: string[], timeout = 3600})
Enables automatic caching of method results. When this method is executed, it first checks the cache. If the cached result exists, it returns the result directly; otherwise, it executes and then returns and stores the result. The elements of the params
array are parameter names, which will be used to get the parameter values, and then concatenated with the cache prefix to form the cache key. For example: @CacheAble("getUser", {params:["name"]})
, if the name
parameter value is tom
, the concatenated cache key is getUser:name:tom
.
@CacheEvict(cacheName: string, {params: string[], delayedDoubleDeletion = true})
Clears the method result cache. The params
parameter is used the same as CacheAble
. delayedDoubleDeletion
is true
to enable delayed double deletion strategy.
GetCacheStore(app: Koatty)
Gets the cache instance, which can manually call get
, set
, and other methods to operate the cache.
Example:
import { CacheAble, CacheEvict, GetCacheStore } from "koatty_cacheable";
@Service()
export class TestService {
@CacheAble("testCache") // Automatically caches the result, cache key=testCache
getTest() {
// todo
}
@CacheEvict("testCache") // Clears the cache before executing setTest(), cache key=testCache
setTest() {
// todo
}
test() {
// Manually operate the cache instance
const store = GetCacheStore(this.app);
store.set(key, value);
}
}
Note: Decorators @CacheAble
and @CacheEvict
cannot be used for controller classes.
Koatty encapsulates a scheduling task library koatty_schedule
, which supports cron expressions and distributed locks based on Redis.
Cron expressions consist of 6 fields, representing seconds, minutes, hours, day of month, month, and day of week:
- Seconds: 0-59
- Minutes: 0-59
- Hours: 0-23
- Day of Month: 1-31
- Months: 1-12 (Jan-Dec)
- Day of Week: 1-7 (Mon-Sun)
@Scheduled(cron: string)
Easily add task execution plans to methods through the @Scheduled
decorator:
import { Scheduled, RedLock } from "koatty_schedule";
export class TestService {
@Scheduled("0 * * * * *")
Test() {
// todo
}
}
In some business scenarios, scheduled tasks cannot be executed concurrently, and the solution is to add a lock. koatty_schedule
implements a distributed lock based on Redis.
RedLock(name?: string, options?: RedLockOptions)
RedLockOptions
:
lockTimeOut?
: Lock timeout in milliseconds, default 10000retryCount?
: Maximum retry count, default 3RedisOptions
: Redis configuration, supports Standalone, Sentinel, Cluster
Example:
import { Scheduled, RedLock } from "koatty_schedule";
export class TestService {
@Scheduled("0 * * * * *")
@RedLock("testCron") // locker
Test() {
// todo
}
}
Because Redis is used, Redis cache configuration is saved in config/db.ts
:
export default {
...
"RedLock": {
host: '127.0.0.1',
port: 6379,
name: "",
username: "",
password: "",
db: 0
},
...
};
You can also pass in the configuration when calling the decorator:
import { Scheduled, RedLock } from "koatty_schedule";
export class TestService {
@Config("redisConf", "db")
private redisConf;
@Scheduled("0 * * * * *")
@RedLock("testCron", {
RedisOptions: redisConf
}) // locker
Test() {
// todo
}
}
Several points to note:
- Decorators
@Scheduled
and@RedLock
cannot be used for controller classes; - Configure corresponding parameters according to the duration of the scheduled task to prevent lock expiration.
- When the lock expires but the business logic has not been completed, the lock will automatically extend for one time. If the extension time expires and the business logic is still not completed, the lock will be released.
Koatty supports gRPC services starting from version 3.4.x.
Use the koatty_cli
command line tool (>=3.4.6):
kt proto hello
This will automatically create src/proto/Hello.proto
. Modify according to the actual situation.
Use the koatty_cli
command line tool (>=3.4.6):
-
Single module mode:
kt controller -t grpc hello
This will automatically create
src/controller/HelloController.ts
. -
Multi-module mode:
kt controller -t grpc admin/hello
This will automatically create
src/controller/Admin/HelloController.ts
.
The controller template code is as follows:
import { KoattyContext, Controller, Autowired, RequestMapping, RequestBody } from 'ko';
import { App } from '../App';
import { SayHelloRequestDto } from '../dto/SayHelloRequestDto';
import { SayHelloReplyDto } from '../dto/SayHelloReplyDto';
@Controller('/Hello') // Consistent with proto.service name
export class HelloController {
app: App;
ctx: KoattyContext;
/**
* Custom constructor
*
*/
constructor(ctx: KoattyContext) {
this.ctx = ctx;
}
/**
* SayHello interface
* Access path: grpc://127.0.0.1/Hello/SayHello
*
* @param {SayHelloRequestDto} data
* @returns
*/
@RequestMapping('/SayHello') // Consistent with proto.service.method name
@Validated() // Parameter validation
SayHello(@RequestBody() params: SayHelloRequestDto): Promise<SayHelloReplyDto> {
const res = new SayHelloReplyDto();
return Promise.resolve(res);
}
}
In addition to the controller file, Koatty will also automatically create RPC protocol input and output DTO classes, such as the aforementioned SayHelloRequestDto
and SayHelloReplyDto
.
Modify config/config.ts
:
export default {
...
protocol: "grpc", // Server protocol 'http' | 'https' | 'http2' | 'grpc' | 'ws' |
...
}
Modify config/router.ts
:
export default {
...
/**
* Other extended configuration
*/
ext: {
protoFile: process.env.APP_PATH + "proto/Hello.proto", // gRPC proto file
}
...
}
OK, now you can start a gRPC server.
Koatty supports WebSocket services starting from version 3.4.x.
Use the koatty_cli
command line tool (>=3.4.6):
-
Single module mode:
kt controller -t ws requst
This will automatically create
src/controller/RequstController.ts
. -
Multi-module mode:
kt controller -t ws admin/requst
This will automatically create
src/controller/Admin/RequstController.ts
.
The controller template code is as follows:
import { KoattyContext, Controller, Autowired, GetMapping } from 'koatty';
import { App } from '../App';
// import { TestService } from '../service/TestService';
...
@Controller('/requst')
export class RequstController {
app: App;
ctx: KoattyContext;
// @Autowired()
// protected TestService: TestService;
/**
* Custom constructor
*
*/
constructor(ctx: KoattyContext) {
this.ctx = ctx;
}
/**
* index interface
* Access path: ws://127.0.0.1/requst
*
* @returns
* @memberof RequstController
*/
@RequestMapping('/')
index(@RequestBody() @Valid("IsEmail") body: string): Promise<any> {
return this.ok('Hi Koatty');
}
}
Modify config/config.ts
:
export default {
...
protocol: "ws", // Server protocol 'http' | 'https' | 'http2' | 'grpc' | 'ws' |
...
}
OK, now you can start a WebSocket server.
During the application startup process, the app
object in the Koatty framework defines a series of events in addition to the events inherent in Koa itself:
- The
appStart
event is triggered after the service starts. We can bind to different events according to project needs. For example, in the scenario of service registration discovery, if hardware failure occurs, you can bind to theappStop
event to handle service deregistration.
app.once("appStop", () => {
// Deregister service
...
})
The role of the @Bootstrap
decorator is to declare the project entry class, which supports passing a function as a parameter. This function will be executed first when the project starts.
@Bootstrap(
// bootstrap function
(app: any) => {
// todo
}
)
export class App extends Koatty {
...
}
Common application scenarios are to handle some runtime environment settings before startup, such as NODE_ENV
. The startup function supports asynchronous execution.
Note: The startup function is executed after the framework's initialize
initialization, at which point the framework's related path attributes (appPath
, rootPath
, etc.) and process.env
have been loaded and set, but other components (plugins, middleware, controllers, etc.) have not been loaded. Be aware when defining the startup function.
In addition to the @Bootstrap
decorator, we can also use the BindEventHook
custom decorator to bind application events (appBoot
, appReady
, appStart
, appStop
) to the startup class.
// src/TestBootstrap.ts:
export function TestBootstrap(): ClassDecorator {
return (target: Function) => {
BindEventHook(AppEvent.appBoot, (app: Koatty) => {
// todo
return Promise.resolve();
}, target)
}
}
Use on the project startup class:
@Bootstrap()
@TestBootstrap()
export class App extends Koatty {
...
}
Note: The function execution of the custom decorator created by BindEventHook
is triggered by the event (appBoot
, appReady
, appStart
, appStop
), and attention should be paid to the framework startup logic and related context.
The entry class of the project can also set two other decorators, which are:
@ComponentScan('./')
: Declares the directory of project components, default is the projectsrc
directory, containing all types of components.@ConfigurationScan('./config')
: Declares the directory of project configuration files, default issrc/config
directory.
IoC stands for Inversion of Control, which translates to control inversion in English. In ES6 Class-style programming, simply creating instances and holding them reveals the following disadvantages:
- To instantiate a component, you must first instantiate dependent components, leading to tight coupling.
- Each component needs to instantiate a dependent component, without reuse.
- Many components need to be destroyed to release resources, such as DataSource. However, if the component is shared by multiple components, how to ensure that all users have been destroyed.
- As more components are introduced, it becomes more difficult to write shared components, and the dependency relationships between them become more complex. If a system has a large number of components, if the lifecycle and interdependent relationships of these components are maintained by the components themselves, it not only greatly increases the complexity of the system but also leads to extremely tight coupling between components, which brings great difficulties to testing and maintenance.
Therefore, the core issues are:
- Who is responsible for creating components?
- Who is responsible for assembling components according to dependency relationships?
- How to correctly destroy components in order of dependency when destroying?
The core solution to this problem is IoC. Referring to the implementation mechanism of Spring IoC, Koatty implements an IOC container (koatty_container
), which automatically classifies and loads components at startup and injects corresponding dependencies according to dependency relationships. Therefore, IoC is also known as Dependency Injection (DI: Dependency Injection), which solves one of the main problems: separating the creation and configuration of components from their use, and managing the lifecycle of components by the IoC container.
According to different application scenarios of components, Koatty divides Beans into four types: COMPONENT
, CONTROLLER
, MIDDLEWARE
, SERVICE
.
-
COMPONENT
: Extension classes, third-party classes belong to this type, such as Plugins, ORM persistence layers, etc. -
MIDDLEWARE
: Middleware classes -
SERVICE
: Service classes
Through the core Loader of the Koatty framework, components are automatically analyzed and assembled at project startup, and the dependency issues between components are automatically handled. The IOC container provides a series of API interfaces for convenient registration and retrieval of assembled Beans.
As the scale of the project expands, circular dependencies are easily introduced. The approach of koatty_container
to solve circular dependencies is lazy loading. koatty_container
binds an appReady
event on the app
, which is used for lazy loading of beans that produce circular dependencies. Attention needs to be paid when using IOC:
app.emit("appReady");
Note: Although lazy loading can solve most scenarios of circular dependencies, it may still fail to assemble in extreme cases. Solutions:
- Try to avoid circular dependencies, introduce new third-party common classes to decouple mutually dependent classes.
- Use the IOC container to get the prototype of the class (
getClass
) and instantiate it manually.
Koatty implements an aspect-oriented programming mechanism based on the IOC container, using decorators and built-in special methods. Encapsulation is achieved through nested functions, which is simple and efficient.
- Decorator declaration: Use
@Before
,@After
,@BeforeEach
,@AfterEach
decorators to declare pointcuts. - Built-in method declaration: Use
__before
,__after
built-in hidden methods to declare pointcuts.
- Declaration method
- Dependency on Aspect Aspect Class
- Ability to use class scope
- Parameter dependency of pointcut method
- Priority
- Usage restrictions
Decorator Declaration
- Dependency: Requires the creation of a corresponding Aspect aspect class to use.
- Ability to use class scope: No
- Parameter dependency of pointcut method: Yes, low
- Priority: Low
- Usable for all types of beans
Built-in Method Declaration
- Dependency: Does not depend on the Aspect aspect class.
- Ability to use class scope: Yes
- Parameter dependency of pointcut method: No, high
- Only usable for
CONTROLLER
type beans, as it depends on thethis
pointer of the class.
Note: If a class uses the decorator @BeforeEach
and this class also contains the __before
method (whether it is its own or inherited from the parent class), then the __before
method has higher priority than the decorator, and the class's decorator @BeforeEach
is invalid (@AfterEach
and __after
are the same).
For example:
@Controller('/')
export class TestController {
app: App;
ctx: KoattyContext;
@Autowired()
protected TestService: TestService;
// Does not depend on the Aspect aspect class
async __before(): Promise<any> { // Does not depend on the specific method's parameters, obtain through this pointer
console.log(this.app); // Can use class scope, obtain the current class attribute through this pointer
console.log(this.ctx)
}
@Before("TestAspect") // Depends on TestAspect aspect class, able to get path parameter
async test(path: string) {
...
}
}
Use koatty_cli
to create:
kt aspect test
This will automatically generate the template code:
import { Aspect } from "koatty";
import { App } from '../App';
@Aspect()
export class TestAspect {
app: App;
run() {
console.log('TestAspect');
}
}
- Name
- Parameter
- Description
- Remarks
@Aspect()
- Identifier: Registered in the IOC container, default is the class name.
- Declare the current class as an aspect class. The aspect class is executed at the pointcut, and the aspect class must implement the
run
method for the pointcut to call. - Only for aspect classes
- Declare the current class as an aspect class. The aspect class is executed at the pointcut, and the aspect class must implement the
@Bootstrap()
- bootFunc: Function to execute before application startup. The specific execution timing is when the
app.on("appReady")
event is triggered.- Declare the current class as a startup class, which is the entry file of the project.
- Only for startup classes
@ComponentScan()
- scanPath: String or string array
- Define the directory that the project needs to automatically load into the container.
- Only for application startup classes
@Component()
- identifier: Registered in the IOC container, default is the class name.
- Define the class as a component class.
- Used for third-party modules or imported classes
@ConfigurationScan()
- scanPath: String or string array, configuration file directory
- Define the directory of project configuration files.
- Only for application startup classes
@Controller()
- path: Binding controller access route
- Define the class as a controller class and bind the route. The default route is
/
. - Only for controller classes
- Define the class as a controller class and bind the route. The default route is
@Service()
- identifier: Registered in the IOC container, default is the class name.
- Define the class as a service class.
- Only for service classes
@Middleware()
- identifier: Registered in the IOC container, default is the class name.
- Define the class as a middleware class.
- Only for middleware classes
@ExceptionHandler()
- Define the class as a global exception handling class.
- Only for exception handling classes
@BeforeEach(aopName:string)
- aopName: Name of the aspect class executing the pointcut
- Declare an aspect for the current class, which will execute the aspect class's
run
method before each method ("constructor", "init", "__before", "__after" excepted) of the current class. - Only for controller classes
- Declare an aspect for the current class, which will execute the aspect class's
@AfterEach(aopName:string)
- aopName: Name of the aspect class executing the pointcut
- Declare an aspect for the current class, which will execute the aspect class's
run
method after each method ("constructor", "init", "__before", "__after" excepted) of the current class. - Only for controller classes
- Declare an aspect for the current class, which will execute the aspect class's
- Name
- Parameter
- Description
- Remarks
@Autowired()
- identifier: Registered in the IOC container, default is the class name.
- type: Type of bean to inject
- constructArgs: Constructor method arguments of the injected bean. If passed, return an instance of the bean automatically injected from the IOC container to the current class.
- isDelay: Whether to delay loading. Delayed loading is mainly to solve circular dependency issues.
- Only for constructor arguments (constructor)
@Config()
- key: Key of the configuration item
- type: Type of the configuration item. The type of the configuration item is automatically defined according to the file where the configuration item is located, e.g., "db" represents the file
db.ts
. - Only for configuration items
- type: Type of the configuration item. The type of the configuration item is automatically defined according to the file where the configuration item is located, e.g., "db" represents the file
@Values()
- val: Value of the attribute, can be a function, the attribute value is the result of the function operation.
- defaultValue: Default value, when val is
Null
,undefined
,NaN
, take the default value. - Used to dynamically modify the attribute value of the class instance.
- Only for class attributes
- defaultValue: Default value, when val is
- Name
- Parameter
- Description
- Remarks
@Before(aopName:string)
- aopName: Name of the aspect class executing the pointcut
- Declare an aspect for the current method, which will execute the aspect class's
run
method before the current method. - Only for controller methods
- Declare an aspect for the current method, which will execute the aspect class's
@After(aopName:string)
- aopName: Name of the aspect class executing the pointcut
- Declare an aspect for the current method, which will execute the aspect class's
run
method after the current method. - Only for controller methods
- Declare an aspect for the current method, which will execute the aspect class's
@RequestMapping()
- path: Bound route
- requestMethod: Bound HTTP request method. Use
RequestMethod
enum data for assignment, e.g.,RequestMethod.GET
. - If set to
RequestMethod.ALL
, it means support all request methods - routerOptions: Configuration items of
koa/_router
- Used for binding routes to controller methods
- Only for controller methods
- requestMethod: Bound HTTP request method. Use
@GetMapping()
- path: Bound route
- routerOptions: Configuration items of
koa/_router
- Used for binding Get routes to controller methods
- Only for controller methods
- routerOptions: Configuration items of
@PostMapping()
- path: Bound route
- routerOptions: Configuration items of
koa/_router
- Used for binding Post routes to controller methods
- Only for controller methods
- routerOptions: Configuration items of
@DeleteMapping()
- path: Bound route
- routerOptions: Configuration items of
koa/_router
- Used for binding Delete routes to controller methods
- Only for controller methods
- routerOptions: Configuration items of
@PutMapping()
- path: Bound route
- routerOptions: Configuration items of
koa/_router
- Used for binding Put routes to controller methods
- Only for controller methods
- routerOptions: Configuration items of
@PatchMapping()
- path: Bound route
- routerOptions: Configuration items of
koa/_router
- Used for binding Patch routes to controller methods
- Only for controller methods
- routerOptions: Configuration items of
@OptionsMapping()
- path: Bound route
- routerOptions: Configuration items of
koa/_router
- Used for binding Options routes to controller methods
- Only for controller methods
- routerOptions: Configuration items of
@HeadMapping()
- path: Bound route
- routerOptions: Configuration items of
koa/_router
- Used for binding Head routes to controller methods
- Only for controller methods
- routerOptions: Configuration items of
@Scheduled()
- cron: Task scheduling configuration
-
Seconds: 0-59 Minutes: 0-59 Hours: 0-23 Day of Month: 1-31 Months: 1-12 (Jan-Dec) Day of Week: 1-7 (Mon-Sun) - Define the execution plan task of the class method.
- Cannot be used for controller methods, dependent on the
koatty_schedule
module.
-
@Validated()
- Used in conjunction with DTO types for parameter validation
- Method parameters without DTO types are not effective, only for controller classes
@RedLock()
- name: Name of the lock
- options: Lock configuration, including Redis server connection configuration
- Define that the method must first obtain a distributed lock (based on Redis) before execution.
- Dependent on the
koatty_schedule
module
@CacheAble()
- cacheName: Cache name
- paramKey: Based on method input parameters as cache key, value is the position of the method input parameter, starting from 0
- redisOptions: Redis server connection configuration
- Dependent on the
koatty_cacheable
module - Cannot be used for controller methods
@CacheEvict()
- cacheName: Cache name
- paramKey: Based on method input parameters as cache key, value is the position of the method input parameter, starting from 0
- eventTime: Time point for clearing the cache
- redisOptions: Redis server connection configuration
- Used together with
@Cacheable
, for clearing the cache when the method is executed - Dependent on the
koatty_cacheable
module - Cannot be used for controller methods
- Name
- Parameter
- Description
- Remarks
@File()
- name: File name
- Get the uploaded file object
- Only for HTTP controller method parameters
@Get()
- name: Parameter name
- Get querystring parameters (get route-bound parameters)
- Only for HTTP controller method parameters
@Header()
- name: Parameter name
- Get Header content
- Only for HTTP controller method parameters
@PathVariable()
- name: Parameter name
- Get route-bound parameters
/user/:id
- Only for HTTP controller method parameters
- Get route-bound parameters
@Post()
- name: Parameter name
- Get Post parameters
- Only for HTTP controller method parameters
@RequestBody()
- Get
ctx.body
- Only for controller method parameters
@RequestParam()
- name: Parameter name
- Get Get or Post parameters, Post takes precedence
- Only for controller method parameters
@Valid()
- rule: Validation rule, supports built-in rules or custom functions
- message: Error message when the rule does not match
- Used for parameter format validation
- Only for controller classes
@Inject()
- paramName: Constructor method argument name (formal parameter)
- cType: Type of bean to inject
- This decorator uses the class constructor method argument to inject dependencies. If used together with
@Autowired()
, it may overwrite the same property injected byautowired
. - Only for constructor arguments (constructor)
Koatty follows the principle that conventions are more important than configuration. To standardize project code and improve robustness, some default standards and conventions have been made.
- Minor version: e.g.,
1.1.1 => 1.1.2
(minor feature additions, bug fixes, etc., downward compatible with 1.1.x) - Middle version: e.g.,
1.1.0 => 1.2.0
(larger feature additions, partial module refactoring, etc. Mainly downward compatible, may have a small number of features incompatible) - Major version: e.g.,
1.0.0 => 2.0.0
(overall design, refactoring, etc. of the framework, not downward compatible) - Stable version: Even-numbered versions at the end are stable versions, odd-numbered versions are unstable versions.
Use Class-style programming including Controller, Service, Model, etc., use Class
instead of function
to organize code. Excludes configurations, tools, function libraries, third-party libraries, etc.
Single file only exports one class
In the project, a single .ts
file only exports once and exports a Class
. Excludes configurations, tools, function libraries, third-party libraries, etc.
Class names must be the same as file names
People familiar with JAVA will not be unfamiliar with this. The class name must be the same as the file name to maintain uniqueness in the IOC container and prevent class overwriting.
No duplicate classes of the same type are allowed
Koatty divides Beans in the IOC container into four types: COMPONENT
, CONTROLLER
, MIDDLEWARE
, SERVICE
.
Beans of the same type cannot have the same class name, otherwise loading will fail.
For example: src/Controller/IndexController.ts
and src/Controller/Test/IndexController.ts
are duplicate classes.
It should be noted that the type of Bean is determined by the decorator, not the filename or directory name. If IndexController.ts
is decorated with @Service()
, then its type is SERVICE
.
For Koatty official components, we recommend using ^
for dependency introduction, and strongly advise against locking versions.
{
"dependencies": {
"koatty_lib": "^1.0.0"
}
}
Comesoon...
-
app
- API documentation
-
ctx
- API documentation
-
IOCContainer
- API documentation
-
Other APIs
- API documentation