Endpoints decorators
#
The routes endpointsAll endpoints are created using decorators from the following list:
@Endpoint(method = 'get'|'post'|'put'|'patch'|'delete'|'options'|'all', path)
- createsendpoint
, pointing to the addresspath
on themethod
method. To create an endpoint via this decorator, you must specify at least the method to be used. Calling this decorator without arguments creates a common endpoint (more details below). Default:path='/'
.@Get(path)
- shortcut for@Endpoint(path, 'get')
@Post(path)
- shortcut for@Endpoint(path, 'post')
@Put(path)
- shortcut for@Endpoint(path, 'put')
@Patch(path)
- shortcut for@Endpoint(path, 'patch')
@Delete(path)
- shortcut for@Endpoint(path, 'delete')
@Options(path)
- shortcut for@Endpoint(path, 'options')
@All(path)
- shortcut for@Endpoint(path, 'all')
All shortcuts decorators are applied over static class methods without a second argument. The use of the second argument applies only to common endpoints (described below), and is used as class decorators.
The path
value can have several levels of nesting, and even contain a typical koa-router
parameter.
As the value of the link, a fragment of the address is used, which characterizes this method exclusively
within the given route node. The full name of the address will be built based on all links,
which preceded the given endpoint
.
The specified decorators are applied as follows:
// ... index.tsimport { Get, Post, Body } from "aom";
class Index { @Get() static Hello() { return `Hello, I'm aom`; }
@Post("/save") static Save(@Body() body: any) { return body; }
@Get("/choose/:variant") static Variant(@Params("variant") variant) { return { variant }; }}
This will create a route node with methods: GET /
, POST /save
and GET /choose/:variant
,
which, after connecting to the route map, will provide access to them using the declared prefixes.
#
Common endpointsaom
allows you to create common endpoints, allowing you to reuse the same code in different
places at a different access address and, if necessary, in a different method.
To declare a class method as a common endpoint, use the @Endpoint()
decorator with no arguments.
Then this method can be used as the second argument for the shortcut decorators applied to the
class in which you want to use these methods.
Example:
// let's create two common endpoints that use the value of the data model from the context stateclass Data { @Endpoint() static List(@State() { model }, @Query() query) { return model.find({ ...query }); }
@Endpoint() static Add(@State() { model }, @Body() body) { return model.create({ ...body }); }}
// create a route node in which the `Users` data model will be declared upon initiation// and connect the previously declared destination points to it by the specified path and methods@Use(Users.Init)@Get("/", Data.List)@Post("/", Data.Add)class Users { model = models.Users;
@Middleware() static Init(@State() state, @This() { model }: Users, @Next() next) { Object.assign(state, { model }); return next(); }}
// create a route node in which the `Customers` data model will be declared upon initiation// and connect the previously declared destination points to it by the specified path and methods@Use(Customers.Init)@Get("/", Data.List)@Post("/", Data.Add)class Customers { model = models.Customers;
@Middleware() static Init(@State() state, @This() { model }: Users, @Next() next) { Object.assign(state, { model }); return next(); }}
Only methods that were invoked as "common" decorators can be used as the second argument for
the Get
/Post
/Put
/Patch
/ Options
/Delete
/All
decorators. Otherwise, the build will
throw an error.
Important! The context of "common" endpoints is the class in which they are declared: that is,
the default @This
decorator will use its own class (in the example, Data
), and the
return value of the @Route()
with different calls will differ only in the values of path
and method
.
@UseNext
#
Composite endpoints: Route endpoints can be formed from building blocks. For example, when implementing mechanisms that imply different conditions for entering the endpoint and the same data structures at the output.
To create a composite destination, the @UseNext
decorator is used, the argument of which is a
pointer to the general endpoint, which will be called if next()
was returned.
Example:
class Auth { login: AuthLogins;
@Post("/login") @Summary("Login/password authentication") @RequestBody({ schema: LoginForm, }) @Requests({ status: 400, schema: ErrorResponse, }) @UseNext(Auth.GenerateTokens) // define the next elements in the chain `Auth.GenerateTokens` method static async Login( @SafeBody(LoginForm) { login, password }: LoginForm, @This() auth: Auth, @Err() err, @Next() next ) { // find a login with the "custom" type auth.login = await AuthLogins.findOne({ login, type: "custom" }); // return an error if the login is not found if (!auth.login) { return err("login not found", 400); } // return an error if the password is incorrect if (auth.login.checkPassword(password)) { return err("wrong password", 400); } // otherwise, go to the next function - generating a token for authorization return next(); }
// create a middleware for checking @Middleware() @Responses({ status: 400, schema: ErrorResponse, }) static async CheckPhoneNumber(@This() auth: Auth, @Body() { login }, @Err() err, @Next() next) { // find a login with the type "phone" auth.login = await AuthLogins.findOne({ login, type: "phone" }); // return error response if login not found if (!auth.login) { return err("login not found", 400); } return next(); }
@Post("/request-code") @Summary("Request authorization by phone number") @RequestBody({ schema: RequestCodeForm, }) @Responses({ status: 200, schema: MessageResponse, }) @Use(Auth.CheckPhoneNumber) // first check that such a login exists static async RequestCode(@This() { login }: Auth) { await login.sendOneOffCode(); // generate and send a one-off code return { message: "Code sent by SMS" }; }
@Post("/confirm-code") @Summary("Confirm authorization by phone number") @RequestBody({ schema: ConfirmCodeForm, }) @Requests({ status: 400, schema: ErrorResponse, }) @Use(Auth.CheckPhoneNumber) // first check that such a login exists @UseNext(Auth.GenerateTokens) // define the next elements in the chain `Auth.GenerateTokens` method static ConfirmCode( @SafeBody(ConfirmCodeForm) { code }: ConfirmCodeForm, @This() { login }: Auth, @Err() err, @Next() next ) { // check that the specified one-off code is valid const codeIsValid = await login.checkOneOffCode(code); // return the error if code not valid if (!codeIsValid) { return err("wrong code", 400); } // otherwise, go to the next function - generating a token for authorization return next(); }
// create common endpoint @Endpoint() // describe returned data schema @Responses({ status: 200, schema: AuthTokens, }) // since it is called after all other checks have passed, all data in it is safe // and can be used directly at the time of the call static GenerateTokens(@This() { login }: Auth) { const { _id: loginId } = login; const newToken = new AuthTokens({ loginId }); const lifetime = 60 * 60 * 1000; // await newToken.generateRandomData(lifetime); return newToken; }}
The length of a compound route can be of any length. In this case, for each common endpoint, add
the @UseNext
decorator specifying the next function to be called, and return the next()
value
in it if its logic is successful.
If a composite route fragment uses middleware
, then they will be added to the cursors call sequence.