Route Handlers¶
Route Handlers are a collection of ExpressJS middleware functions whose job it is to process an incoming request for a given RESTful endpoint. Collections of handlers are grouped together by a common name or schema, depending on the OpenAPI specification defined.
Composer makes heavy use of both functional and aspect oriented programming techniques. Each Operation defined in the OpenAPI specification is generated to a single function and is decorated with the necessary TypeScript decorators to indicate their function.
Anatomy of a Route¶
For example, given the following Operation object defined in an OpenAPI specification file is for a GET
request
to retrieve a single object resource of the Pet
schema given a specified unique identifier.
1 /pet/{id}:
2 x-schema: Pet
3 parameters:
4 - $ref: "#/components/parameters/id"
5 get:
6 description: Returns a single Pet from the system that the user has access to
7 x-name: findById
8 responses:
9 "200":
10 description: A Pet object.
11 content:
12 application/json:
13 schema:
14 $ref: "#/components/schemas/Pet"
The resulting route handler class and function as generated by Composer will look like the following.
1 /**
2 * Handles all REST API requests for the endpoint `/pet`.
3 *
4 * @author AcceleratXR, Inc.
5 */
6 @Model(Pet)
7 @Route("/pet")
8 class PetRoute extends ModelRoute<Pet> {
9 ...
10
11 /**
12 * Returns a single Pet from the system that the user has access to
13 */
14 @Get("/:id")
15 private async findById(@Param("id") id: string, @AuthUser user?: JWTUser): Promise<Pet | undefined> {
16 return super.doFindById(id, user);
17 }
18
19 ...
20 }
First notice how the class itself is constructed. The Path Item in the OpenAPI specification specifies an x-schema
field with value Pet
. This means that all Path Items that also specify Pet
will be grouped into the same
PetRoute.ts
file.
The @Model(Pet)
decorator is used to identify that this Route handler class is associated with the Pet
data
model. When the Composer server starts up and scans this class it therefore uses this information to bind the correct
datastore connection.
The class also uses the @Route
decorator. This indicates to the Server that the class is responsible for processing
RESTful endpoints. The decorator can take either a single string value or an array as its sole parameter corresponding
to the root path patterns that the handler will respond to. In this example, this route handler is responsible for
processing all incoming requests to the /pet
path.
Finally we get to the endpoint handler itself, findById
. The name findById
directly corresponds to the x-name
value specified in the Operation object’s definition. Further, the HTTP method is denoted by the @Get
decorator and
take an optional argument /:id
. This path gets appended to the root defined at the top of the class /pet/:id
when
the class is processed by the Server and registered with ExpressJS. Each function defined with a corresponding HTTP method
decorator is registered to ExpressJS accordingly as a middleware function. Should the handler also make use of the
@Before
and @After
decorators, these additional functions will be registered respectively with the route handler
function as additional middleware for the given endpoint path.
The function itself takes multiple arguments, namely @Param("id") id: string, @AuthUser user?: JWTUser
. The First
argument with the @Param
decorator indicates that the path contains one or more parameters, one of which is named id
and that the server should pull that value out of the request path and pass its value in directly to this argument.
Therefore, if the HTTP request that arrives is for the path /pet/scotty
then the value of the id
argument will be
scotty
.
The second argument is for the authorized user that performed the request as denoted by the @AuthUser
decorator. As this
is an optional argument, a value is only passed in when a user has actually authenticated with the service and is valid. In
all other cases this value will be undefined
to indicate that no valid user is attempting to perform this action.
The final thing to notice about this function is the body and return type. The return type is of type
Promise<Pet | undefined>
. This indicates to the server that the handler is async and will return a Pet
object if the
object was found with the given id or undefined
object if it could not be at some point in the future. The return of the
function does not have to be a Promise
and in fact can be a direct value. The server will automatically adjust its behavior
accordingly. When one of these values is returned, the server will automatically encode the object as JSON and return it to
the client.
The body of the function makes a single call to super.doFindById(id, user)
. Notice that the class’s definition
inherits from ModelRoute
. This is a base class to which all Model route handlers can extend to provide common built-in
behavior such as this. It’s purpose is to reduce the amount of code needed for common data oriented REST APIs. In this
particular case we are leveraging the built-in function doFindById
. This function performs generic logic for retrieving
a single record of the desired type from the datastore. The built-in will also handle validating permissions for accessing
the object for the authorized user when Access Control Lists are enabled.
Route Decorators¶
The following is a list of decorators that can used within a Route handler class to perform various HTTP processing behavior.
@Route
¶
The @Route
decorator is used to indicate that a given class contains one or more endpoint handlers for a given set
of paths.
1/**
2 * Handles all REST API requests for the endpoint `/pet`.
3 *
4 * @author <AUTHOR>
5 */
6@Model(Pet)
7@Route("/pet")
8class PetRoute extends ModelRoute<Pet> {
9}
@Init
¶
The @Init
decorator indicates a function within a Route handler class that should be called at service startup in
order to initialize some state.
1/**
2 * Called on server startup to initialize the route with any defaults.
3 */
4 @Init
5 private async initialize() {
6 // TODO Add business logic here
7 }
@Auth
¶
The @Auth
decorator is used to indicate that the decorated endpoint handle requires authentication by one of the
specified methods.
1 /**
2 * Add a new pet to the store
3 */
4 @Auth(["jwt"])
5 @Post()
6 private async add(obj: Pet, @AuthUser user?: JWTUser): Promise<Pet> {
7 const newObj: Pet = new Pet(obj);
8
9 throw new Error("This route is not implemented.");
10 }
@Before
¶
This decorator is used to indicate additional middleware functions that should be executed before the main endpoint handler is executed. This is typically used for input pre-processing and validation.
1 /**
2 * Add a new pet to the store
3 */
4 @Auth(["jwt"])
5 @Before(["validate"])
6 @After(["prepareOutput"])
7 @Post()
8 private async add(obj: Pet, @AuthUser user?: JWTUser): Promise<Pet> {
9 const newObj: Pet = new Pet(obj);
10
11 throw new Error("This route is not implemented.");
12 }
@After
¶
The @After
decorator is used to indicate additional middleware functions that should be expected after the main
endpoint handler is executed. This is typically used for post-processing and data preparation before returning to the
client.
1 /**
2 * Add a new pet to the store
3 */
4 @Auth(["jwt"])
5 @Before(["validate"])
6 @After(["prepareOutput"])
7 @Post()
8 private async add(obj: Pet, @AuthUser user?: JWTUser): Promise<Pet> {
9 const newObj: Pet = new Pet(obj);
10
11 throw new Error("This route is not implemented.");
12 }
@Delete
¶
The @Delete
decorator is used to indicate that the handler function will process HTTP requests with method
DELETE
. It takes an optional parameter to provide a sub-path.
1 /**
2 * Deletes the Pet
3 */
4 @Auth(["jwt"])
5 @Delete("/:id")
6 private async delete(@Param("id") id: string, @AuthUser user?: JWTUser): Promise<void> {
7 return super.doDelete(id, user);
8 }
@Get
¶
The @Get
decorator is used to indicate that the handler function will process HTTP requests with method
GET
. It takes an optional parameter to provide a sub-path.
1 /**
2 * Multiple Pet objects
3 */
4 @Get()
5 private async find(@AuthUser user?: JWTUser): Promise<Array<Pet>> {
6 throw new Error("This route is not implemented.");
7 }
@Post
¶
The @Post
decorator is used to indicate that the handler function will process HTTP requests with method
POST
. It takes an optional parameter to provide a sub-path.
1 /**
2 * Add a new pet to the store
3 */
4 @Auth(["jwt"])
5 @Post()
6 private async add(obj: Pet, @AuthUser user?: JWTUser): Promise<Pet> {
7 const newObj: Pet = new Pet(obj);
8
9 throw new Error("This route is not implemented.");
10 }
@Put
¶
The @Put
decorator is used to indicate that the handler function will process HTTP requests with method
PUT
. It takes an optional parameter to provide a sub-path.
1 /**
2 * Updates a single Pet
3 */
4 @Auth(["jwt"])
5 @Put("/:id")
6 @Validate("validate")
7 private async update(@Param("id") id: string, obj: Pet, @AuthUser user?: JWTUser): Promise<Pet> {
8 const newObj: Pet = new Pet(obj);
9
10 return super.doUpdate(id, newObj, user);
11 }
@Validate
¶
This decorator is used to indicate a middleware function that will execute before the main endpoint handler in order to validate the incoming data. It must be the name of a function within the class itself.
1/**
2 * Determines if the specified request payload is valid and can be accepted.
3 *
4 * @throws When the request payload contains invalid input or data.
5 */
6 private validate(data: Pet): void {
7 // TODO Validate input data
8 }
9
10 /**
11 * Updates a single Pet
12 */
13 @Auth(["jwt"])
14 @Put("/:id")
15 @Validate("validate")
16 private async update(@Param("id") id: string, obj: Pet, @AuthUser user?: JWTUser): Promise<Pet> {
17 const newObj: Pet = new Pet(obj);
18
19 return super.doUpdate(id, newObj, user);
20 }
@WebSocket
¶
The @WebSocket
decorator is used to indicate that the handler function will process HTTP Upgrade requests in order
to establish a WebSocket connection with the client. It takes an optional parameter to provide a sub-path.
1 /**
2 * Create a WebSocket connection with the client.
3 */
4 @WebSocket()
5 private async connect(@Socket ws: ws, @AuthUser user?: JWTUser): Promise<Array<Pet>> {
6 ws.on("message", (msg) => {
7 ws.send(`echo ${msg}`);
8 });
9 ws.send(`hello ${user ? user.uid : "guest"}`);
10 }