Understanding the routing
Routing is a very important topic that shaped on best-practices in the Rest API world. In this chapter, we are going to talk about route management at Axe API.
- You will learn
- What is routing?
- How Axe API creates routes?
- How to manage related routes?
- How to enable/disable route for a specific handler?
- How to create custom route?
Routing in Express.js
Routing refers to determining how an application responds to a client request to a particular endpoint.
You can see a simple Express.js routing example in the following code block;
app.get("/api/v1/users", (req, res) => {
res.json({
id: 1,
// ...
});
});In the example above, a simple HTTP route is defined for the GET method. If the client sends the cURL request to the Express server, it gets the following response.
$ curl \
-H "Content-Type: application/json" \
-X GET http://localhost:3000/api/v1/users{
"id": 1
// ...
}In most web frameworks, developers must define all routes manually. But there are many articles on how to create a good route for a Rest API. If you show a database schema to a developer, most of the time they can figure out more or less how routing should be.
That's why Axe API has an auto-route creation mechanism. Axe API core reads your models, and analyzes their relationships, then creates routes and handles HTTP requests.
Model-based routes
Let's start with fundamentals, and create a simple Axe API model;
import { Model } from "axe-api";
class Users extends Model {}
export default User;Axe API practices the following task when you run the application;
- Analyzes
Usermodel - Creates all possible routes
- Listens all created routes
For the example model, the application is ready to handle the following routes;
| HTTP Method | Route | Description |
|---|---|---|
POST | api/v1/users | Create a new item |
GET | api/v1/users | Paginate all items |
GET | api/v1/users/:id | Fetching an item |
PUT | api/v1/users/:id | Update an item |
DELETE | api/v1/users/:id | Delete an item |
Developers don't have to create routes manually unlike other web frameworks, the only required thing is creating model files.
TIP
Axe API uses the plural version of your model name in routes. That's why using a singular name in model names is suggested.
Model relations
Let's assume that we have some tables that are related to each other, like the following example;

You can define a relationship between models to create related routes;
import { Model } from "axe-api";
class User extends Model {
posts() {
return this.hasMany("Post", "id", "user_id");
}
}
export default User;import { Model } from "axe-api";
class Post extends Model {
user() {
return this.belongsTo("User", "user_id", "id");
}
}
export default Post;We defined two models; User and Post.
In the User model, we defined a hasMany relationship. By that definition, we aim to create a relation from the User model to the Post model. Which means that every user might have many posts.
In the Post model, we defined a belongsTo relationship. This means every post might have only one related user record.
Axe API creates automatically the following routes by this model definition;
| HTTP Method | Url | Description |
|---|---|---|
GET | api/v1/users | Paginate users |
POST | api/v1/users | Create a new user |
GET | api/v1/users/:id | Get a user by id |
PUT | api/v1/users/:id | Update a user by id |
DELETE | api/v1/users/:id | Delete a user by id |
GET | api/v1/users/:userId/posts | Paginate posts |
POST | api/v1/users/:userId/posts | Create a new post |
GET | api/v1/users/:userId/posts/:id | Get a post by id |
PUT | api/v1/users/:userId/posts/:id | Update a post by id |
DELETE | api/v1/users/:userId/posts/:id | Delete a post by id |
Select the relation name
In a model file, you can override the base Axe API model's getters.
All functions other than getters that are defined in a model file would be accepted as a relationship definition. In the creating relation route process, the function name would be used in the route definition.
Let's look at the following definition;
import { Model } from "axe-api";
class User extends Model {
tasks() {
return this.hasMany("Task", "id", "created_user_id");
}
}By this definition, the route name will be generated as api/v1/users/:userId/tasks. As you can see, the function name is used directly in the route.
Let's assume that we create two relationship functions for the same child model, with different names;
import { Model } from "axe-api";
class User extends Model {
createdTasks() {
return this.hasMany("Task", "id", "created_user_id");
}
assignedTasks() {
return this.hasMany("Task", "id", "assigned_user_id");
}
}In this example, Axe API creates the following route structures;
| Route structure | Model | Foreign key |
|---|---|---|
api/v1/users/:id | User | |
api/v1/users/:userId/created-tasks/:id | Task | created_user_id |
api/v1/users/:userId/assigned-tasks/:id | Task | assigned_user_id |
As you can see, Axe API uses the function name by converting it to param-case.
Route tree structure
It is important to understand that Axe API creates a route tree by relationship definitions.
If a model (let's call it as User) has a has-many relationship (let's assume to the Post model), which means that that model (User) will be top of the three (/api/v1/users/:id/posts).
Also, the Comment model would be the child of the Post model if you defined it as a hasMany relationship in the Post model.
Let's check the following model relations;
import { Model } from "axe-api";
class User extends Model {
posts() {
return this.hasMany("Post", "id", "user_id");
}
}
export default User;import { Model } from "axe-api";
class Post extends Model {
user() {
return this.belongsTo("User", "user_id", "id");
}
comments() {
return this.hasMany("Comment", "id", "post_id");
}
}
export default Post;import { Model } from "axe-api";
class Comment extends Model {
post() {
return this.belongsTo("Post", "post_id", "id");
}
}
export default Comment;Your model tree would be like the following schema by your relationship definitions.

As a general rule, the models that have not been defined in a has-many relationship by other models would be on the top level.
Adding a custom route
Axe API allows you to define custom routes in the app/v1/init.ts.
import { Express } from "express";
const onBeforeInit = async (app: Express) => {
app.get("/api/health", (req, res) => {
res.json({
status: true,
});
});
};
const onAfterInit = async (app: Express) => {};
export { onBeforeInit, onAfterInit };By using the initialization file, you can create very specific routes, to implement your custom logic.
What is HandlerTypes?
HandlerTypes is a specific name of a route to describe the request. Because using HTTP Method doesn't describe clearly the aim of an HTTP request.
For example, an HTTP GET request might aim to fetch a single data, or paginate all items. HandlerTypes in the Axe API ecosystem describes the aim of the HTTP request.
Let's check the following table;
| HTTP Method | Route | HandlerTypes | Description |
|---|---|---|---|
POST | api/v1/users | INSERT | Create new item |
GET | api/v1/users | PAGINATE | Paginate items |
GET | api/v1/users/all | ALL | Fetch all items |
GET | api/v1/users/:id | SHOW | Fetch an item |
PUT | api/v1/users/:id | UPDATE | Update an item |
PATCH | api/v1/users/:id | PATCH | Patch an item |
DELETE | api/v1/users/:id | DELETE | Delete an item |
DELETE | api/v1/users/:id/force | FORCE_DELETE | Force delete an item |
A HandlerTypes is the name of a specific HTTP request.
You can define which HandlerTypes should be used to create automatic routes in an model.
import { Model, HandlerTypes } from "axe-api";
class User extends Model {
get handlers() {
return [HandlerTypes.PAGINATE];
}
}
export default User;By this definition, the only HTTP route that will be created is the following one;
| HTTP Method | Route | Description |
|---|---|---|
GET | api/v1/users | Paginate all items |
You can decide what handlers should be created by using the model definition.
Next step
In this section, we tried to explain the fundamentals of routing. Next section, we are going to talk about version management.
Axe API