Create rest API quickly with Axe API framework: Part 2

Pavel Salauyou
5 min readOct 10, 2023

--

In the previous article we detailed how to install and run the Axe API, we have a fully working API but at the moment it does not have any models and routers. In this article we will work with the database. We will create models, routers and make http requests to the API.

Before creating models and roots, we need to create tables in the database that will refer to our models. Axe API uses the knex.js library for database operations and migrations. Therefore, you should install the knex CLI on your computer:

npm install -g knex

Now you can create migrations that describe which columns will be in the tables:

knex --esm migrate:make 1User
knex --esm migrate:make 2Menu
knex --esm migrate:make 3MenuItem
knex --esm migrate:make 4Order

After running the commands, you will see the four files created in the migrations folder. In these files you need to place the code to create tables you can read the knex documentation or just copy the code below:

migrations/<timeprefix>_User.js

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function(knex) {
return knex.schema.createTable('user', function (table) {
table.increments();
table.string('email').notNullable().unique().index();
table.string('password').notNullable();
table.string('firstName').notNullable();
table.string('lastName').notNullable();
table.timestamps();
});
};

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function(knex) {
return knex.schema.dropTable('user');
};

migrations/<timeprefix>_Menu.js

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function(knex) {
return knex.schema.createTable('menus', function (table) {
table.increments();
table.string('name').notNullable().unique().index();
table.string('description').notNullable();
table.timestamps();
});
};

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function(knex) {
return knex.schema.dropTable('menus');
};

migrations/<timeprefix>_MenuItem.js

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function(knex) {
return knex.schema.createTable('menu_items', function (table) {
table.increments();
table.string('name').notNullable().unique().index();
table.string('price').notNullable();
table.integer('menu_id').unsigned().notNullable();
table.timestamps();
table
.foreign('menu_id')
.references('menus.id')
.onDelete('CASCADE')
.onUpdate('CASCADE');
});
};

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function(knex) {
return knex.schema.dropTable('menu_items');
};

migrations/<timeprefix>_Order.js

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function(knex) {
return knex.schema.createTable('orders', function (table) {
table.increments();
table.integer('menu_item_id').unsigned().notNullable();
table.integer('user_id').unsigned().notNullable();
table.integer('quantity').notNullable().defaultTo(1);
table.timestamps();

table
.foreign('menu_item_id')
.references('menu_items.id')
.onDelete('CASCADE')
.onUpdate('CASCADE');

table
.foreign('user_id')
.references('users.id')
.onDelete('CASCADE')
.onUpdate('CASCADE');
});
};

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function(knex) {
return knex.schema.dropTable('orders');
};

Now you can run database migrations:

knex --esm migrate:latest

After running the command, the console should display Batch 1 run: 4 migrations, which means that the tables were successfully created.

If you are using mysql 8, you may have this error ER_NOT_SUPPORTED_AUTH_MODE to solve it, go to the mysql console and run the command:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';

Setting up models

Now, we need to set up models located in the Models folder under the app directory. In Axe API, you can have different versions of your API using the same database layout. That's why you'll see the app/v1 folder in your project.

Let’s create model files for all the tables:

app/v1/Models/User.ts

import { Model } from 'axe-api';

class User extends Model {
}

export default User;

app/v1/Models/Menu.ts

import { Model } from 'axe-api';

class Menu extends Model {
}

export default Menu;

app/v1/Models/MenuItem.ts

import { Model } from 'axe-api';

class MenuItem extends Model {
}

export default MenuItem;

app/v1/Models/Order.ts

import { Model } from 'axe-api';

class Order extends Model {
}

export default Order;

After creating the models, you can run the application and check which routers have appeared:

npm run start:dev

Now follow the link http://localhost:3000/routes and you will see all available routers:

[
"POST /api/v1/menus",
"GET /api/v1/menus",
"GET /api/v1/menus/:id",
"PUT /api/v1/menus/:id",
"PATCH /api/v1/menus/:id",
"DELETE /api/v1/menus/:id",
"POST /api/v1/menu-items",
"GET /api/v1/menu-items",
"GET /api/v1/menu-items/:id",
"PUT /api/v1/menu-items/:id",
"PATCH /api/v1/menu-items/:id",
"DELETE /api/v1/menu-items/:id",
"POST /api/v1/orders",
"GET /api/v1/orders",
"GET /api/v1/orders/:id",
"PUT /api/v1/orders/:id",
"PATCH /api/v1/orders/:id",
"DELETE /api/v1/orders/:id",
"POST /api/v1/users",
"GET /api/v1/users",
"GET /api/v1/users/:id",
"PUT /api/v1/users/:id",
"PATCH /api/v1/users/:id",
"DELETE /api/v1/users/:id"
]

Follow the link http://localhost:3000/api/v1/menus and you will see the response in json format and it will contain the data and already ready pagination:

{
"data": [],
"pagination": {
"total": 0,
"lastPage": 0,
"prevPage": null,
"nextPage": null,
"perPage": 10,
"currentPage": 1,
"from": 0,
"to": 0
}
}

At the moment we have written very little code and already have a fully working API. Axe API has analysed the models and automatically built the routers.

Now we can add additional logic to our models as well as create, delete and perform other CRUD operations in our API.

Adding new data

Currently, the models are set up in a way that only allows them to get information and not accept any new data. This is done for security reasons to prevent unauthorized access. To work with these models, developers need to specify which parts of the data can be updated by clients and define rules for checking if the provided data is valid.

The next step in the process is to add certain functions called fillable and validation getters, which will help manage and validate the data being handled by the models.

app/v1/Models/User.ts

import { Model } from 'axe-api';

class User extends Model {
get fillable() {
return ['email', 'firstName', 'lastName', 'password'];
}

get validations() {
return {
email: 'required|min:3|max:100|email',
firstName: 'required|min:2|max:50',
lastName: 'required|min:2|max:50',
password: 'required|min:6|max:100',
};
}
}

export default User;

app/v1/Models/Menu.ts

import { Model } from 'axe-api';

class Menu extends Model {
get fillable() {
return ['name', 'description'];
}

get validations() {
return {
name: 'required|min:3|max:50',
description: 'required|min:2|max:255'
};
}
}

export default Menu;

app/v1/Models/MenuItem.ts

import { Model } from 'axe-api';

class MenuItem extends Model {
get fillable() {
return ['name', 'price', 'menu_id'];
}

get validations() {
return {
name: 'required|min:2|max:50',
price: 'required|numeric',
menu_id: 'required|numeric',
};
}
}

export default MenuItem;

app/v1/Models/Order.ts

import { Model } from 'axe-api';

class Order extends Model {
get fillable() {
return ['quantity', 'menu_item_id', 'user_id'];
}

get validations() {
return {
quantity: 'required|numeric',
menu_item_id: 'required|numeric',
user_id: 'required|numeric',
};
}
}

export default Order;

By using these definitions, we basically let the Axe API know which fields can be filled and what rules it should follow to check if the data is valid.

Now, let’s try to create a new user without entering any information first (to make requests to the api, I recommend using postman):

curl \
-H "Content-Type: application/json" \
-X POST http://localhost:3000/api/v1/users

Response:

{
"errors": {
"email": [
"The email field is required."
],
"firstName": [
"The firstName field is required."
],
"lastName": [
"The lastName field is required."
],
"password": [
"The password field is required."
]
}
}

After the cURL request, you will encounter the following error message, which is a validation error triggered by the Axe API.

To create a valid user, use the following cURL request. If the request is successful, you will receive an HTTP response showing the newly created user record.

curl \
-d '{"email": "william.marcus@example.com", "firstName": "William", "lastName":"Marcus", "password": "my-secret-password"}' \
-H "Content-Type: application/json" \
-X POST http://localhost:3000/api/v1/users

You can simply copy and paste this Curl query into postman. Postman will automatically parse this query and generate the correct headers.

Response:

{
"id": 1,
"email": "william.marcus@example.com",
"password": "my-secret-password",
"firstName": "William",
"lastName": "Marcus",
"created_at": "2023-07-20T12:26:28.000Z",
"updated_at": "2023-07-20T12:26:28.000Z"
}

In this article we learnt how to create migrations and tables in databases, validate models and make real http requests to add and retrieve data from API, in the next article we will continue to add data and learn how to work with related data.

--

--