axe-api 0.18.0 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,7 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
14
- node-version: [14.x, 16.x]
14
+ node-version: [14.x, 16.x, 17.x]
15
15
  database: [mysql8, mysql57, "postgres"]
16
16
  steps:
17
17
  - uses: actions/checkout@v2
@@ -11,7 +11,7 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
14
- node-version: [14.x, 16.x]
14
+ node-version: [14.x, 16.x, 17.x]
15
15
  steps:
16
16
  - uses: actions/checkout@v2
17
17
  - name: Use Node.js ${{ matrix.node-version }}
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Release Notes
2
2
 
3
+ ## [0.19.2 (2022-01-22)](https://github.com/axe-api/axe-api/compare/0.19.2...0.19.1)
4
+
5
+ ### Fixed
6
+
7
+ - Fixed the calling `onBeforePaginate` and `onBeforeShow` hooks bug.
8
+
9
+ ## [0.19.1 (2022-01-22)](https://github.com/axe-api/axe-api/compare/0.19.1...0.19.0)
10
+
11
+ ### Fixed
12
+
13
+ - knex.js version update.
14
+
15
+ ## [0.19.0 (2021-12-05)](https://github.com/axe-api/axe-api/compare/0.19.0...0.18.1)
16
+
17
+ ### Fixed
18
+
19
+ - [#110](https://github.com/axe-api/axe-api/issues/110)
20
+
21
+ ### Enhancements
22
+
23
+ - [#106](https://github.com/axe-api/axe-api/issues/106)
24
+
25
+ ## [0.18.1 (2021-12-02)](https://github.com/axe-api/axe-api/compare/0.18.1...0.18.0)
26
+
27
+ ### Fixed
28
+
29
+ - [#117](https://github.com/axe-api/axe-api/issues/117)
30
+
3
31
  ## [0.18.0 (2021-11-30)](https://github.com/axe-api/axe-api/compare/0.18.0...0.17.5)
4
32
 
5
33
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axe-api",
3
- "version": "0.18.0",
3
+ "version": "0.19.2",
4
4
  "description": "AXE API is a simple tool which has been created based on Express and Knex.js to create Rest APIs quickly.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -20,37 +20,37 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "change-case": "^4.1.2",
23
- "dotenv": "^10.0.0",
24
- "express": "^4.17.1",
25
- "knex": "^0.95.12",
23
+ "dotenv": "^14.2.0",
24
+ "express": "^4.17.2",
25
+ "knex": "^1.0.1",
26
26
  "knex-paginate": "^3.0.0",
27
- "knex-schema-inspector": "^1.6.4",
27
+ "knex-schema-inspector": "^1.7.1",
28
28
  "pluralize": "^8.0.0",
29
29
  "validatorjs": "^3.22.1"
30
30
  },
31
31
  "devDependencies": {
32
- "@babel/cli": "^7.14.8",
33
- "@babel/core": "^7.14.8",
34
- "@babel/node": "^7.14.7",
35
- "@babel/preset-env": "^7.14.8",
36
- "@babel/runtime": "^7.14.8",
37
- "babel-jest": "^27.0.6",
38
- "babel-loader": "^8.2.2",
32
+ "@babel/cli": "^7.16.8",
33
+ "@babel/core": "^7.16.10",
34
+ "@babel/node": "^7.16.8",
35
+ "@babel/preset-env": "^7.16.11",
36
+ "@babel/runtime": "^7.16.7",
37
+ "babel-jest": "^27.4.6",
38
+ "babel-loader": "^8.2.3",
39
39
  "babel-plugin-module-resolver": "^4.1.0",
40
40
  "babel-preset-minify": "^0.5.1",
41
41
  "eslint": "^7.31.0",
42
42
  "eslint-config-standard": "^16.0.3",
43
- "eslint-plugin-import": "^2.23.4",
43
+ "eslint-plugin-import": "^2.25.4",
44
44
  "eslint-plugin-node": "^11.1.0",
45
45
  "eslint-plugin-promise": "^5.1.0",
46
46
  "eslint-plugin-standard": "^5.0.0",
47
47
  "eslint-plugin-unicorn": "^33.0.1",
48
48
  "eslint-watch": "^7.0.0",
49
- "jest": "^27.0.6",
49
+ "jest": "^27.4.7",
50
50
  "mysql": "^2.18.1",
51
- "nodemon": "^2.0.12",
52
- "pg": "^8.6.0",
53
- "set-value": ">=4.0.0",
51
+ "nodemon": "^2.0.15",
52
+ "pg": "^8.7.1",
53
+ "set-value": ">=4.1.0",
54
54
  "sqlite3": "^5.0.2"
55
55
  }
56
56
  }
package/readme.md CHANGED
@@ -23,21 +23,98 @@
23
23
  </a>
24
24
  </h1>
25
25
 
26
- > This project is under development and not ready for production.
26
+ The fastest way to create Rest API, by defining database models and relations.
27
27
 
28
- Fastest way to create simple Rest API by defining database models and relations.
28
+ > Axe API has great documentation. Please [check it out in here](https://axe-api.github.io/).
29
29
 
30
- ## Key Features
30
+ ## What Is Axe API?
31
31
 
32
- - Automatic route creating
33
- - Automatic route handling
34
- - Form validation support
35
- - Middlewares
36
- - Strong query features
37
- - Recursive resources
38
- - The extendable business logic structure
39
- - Multiple database support (Postgres, MSSQL, MySQL, MariaDB, SQLite3, Oracle, and Amazon Redshift)
40
- - Well documented
32
+ **Axe API** is the _fastest_ way to create **Rest API** by defining only database models and relationships between them. It is built on [Knex.js](http://knexjs.org), and its awesome active records pattern. On the other hand, you have another familiar thing, [Express](https://expressjs.com/).
33
+
34
+ You are going to be able to develop an API **10 times faster** with **Axe API**!
35
+
36
+ ## How It Works?
37
+
38
+ [Express](https://expressjs.com/) and [Knex.js](http://knexjs.org) are great tools to create [Node.js](https://nodejs.org) based applications. But usually, we code too much the same things to design an API. We aim to reduce code duplication and give you speed by using Axe API.
39
+
40
+ Axe API provides you the ability to separate your common tasks to build an API from your business logic. **Axe API** expects model definitions to analyze your routing structure. After you created your models and their relations between them, Axe API can handle all _well-known_ API requests. Creating an API with 5 tables takes almost 15 minutes.
41
+
42
+ Shortly, **Axe API** performs three basic functions;
43
+
44
+ - **Analyzes** your models and their relationships to create routes.
45
+ - **Handles** all HTTP requests.
46
+ - **Separate** your business logic from API best practices.
47
+
48
+ Let's assume that you have a model like this;
49
+
50
+ ```js
51
+ import { Model } from "axe-api";
52
+
53
+ class User extends Model {}
54
+ ```
55
+
56
+ With this model, you will have all of the basic API routes for **User** resources. **Axe API** will create **CRUD** routes for you in the _booting_ process and these routes would be completely ready to be handled and processed by Axe API. The following routes will be handled automatically;
57
+
58
+ - `POST api/users`
59
+ - `GET api/users`
60
+ - `GET api/users/:id`
61
+ - `PUT api/users/:id`
62
+ - `DELETE api/users/:id`
63
+
64
+ This is the magic of **Axe API**!
65
+
66
+ ## Installation
67
+
68
+ Using **Axe API** in an application is very easy. We've created a CLI tool for you; [axe-magic](https://github.com/axe-api/axe-magic).
69
+
70
+ You can create a new Axe API project by using [axe-magic](https://github.com/axe-api/axe-magic). But first, you can install it in your development environment. When you installed it, you can be able to access **axe-magic** command via CLI. You can use the following command to install **axe-magic** to your machine;
71
+
72
+ ```bash
73
+ $ npm i -g axe-magic
74
+ $ axe-magic --version
75
+ 1.0.0
76
+ ```
77
+
78
+ After that, creating a new project is very easy. Just you can execute the following command;
79
+
80
+ ```bash
81
+ $ axe-magic new my-api
82
+ ```
83
+
84
+ This command will pull [axe-api-template](https://github.com/axe-api/axe-api-template) project to your current directory with a new name, **my-api**.
85
+
86
+ To install your project's depencies, you can execute the following commands in the root directory;
87
+
88
+ ```bash
89
+ $ cd my-api
90
+ $ npm install
91
+ ```
92
+
93
+ To serve this application, you can execute the following command;
94
+
95
+ ```bash
96
+ $ npm run start:dev
97
+ ```
98
+
99
+ > `start:dev` command use [nodemon](https://www.npmjs.com/package/nodemon). If you haven't installed it yet, we suggest you install it first.
100
+
101
+ After that, your first **Axe API** application will be running in `localhost:3000`.
102
+
103
+ You will see the following API response if you visit [localhost:3000](http://localhost:3000).
104
+
105
+ ```json
106
+ {
107
+ "name": "AXE API",
108
+ "description": "The best API creation tool in the world.",
109
+ "aim": "To kill them all!"
110
+ }
111
+ ```
112
+
113
+ If you can see that response, it means that your project is running properly.
114
+
115
+ ## Documentation
116
+
117
+ Axe API has great documentation. Please [check it out in here](https://axe-api.github.io/).
41
118
 
42
119
  ## How To Run Integration Tests
43
120
 
package/src/constants.js CHANGED
@@ -13,6 +13,7 @@ const HOOK_FUNCTIONS = {
13
13
  onBeforeDeleteQuery: "onBeforeDeleteQuery",
14
14
  onBeforeDelete: "onBeforeDelete",
15
15
  onBeforePaginate: "onBeforePaginate",
16
+ onBeforeAll: "onBeforeAll",
16
17
  onBeforeShow: "onBeforeShow",
17
18
  onAfterInsert: "onAfterInsert",
18
19
  onAfterUpdateQuery: "onAfterUpdateQuery",
@@ -20,6 +21,7 @@ const HOOK_FUNCTIONS = {
20
21
  onAfterDeleteQuery: "onAfterDeleteQuery",
21
22
  onAfterDelete: "onAfterDelete",
22
23
  onAfterPaginate: "onAfterPaginate",
24
+ onAfterAll: "onAfterAll",
23
25
  onAfterShow: "onAfterShow",
24
26
  };
25
27
 
@@ -52,7 +54,8 @@ const HANDLERS = {
52
54
  SHOW: "show",
53
55
  UPDATE: "update",
54
56
  DELETE: "destroy",
55
- AUTOSAVE: "autosave",
57
+ PATCH: "patch",
58
+ ALL: "all",
56
59
  };
57
60
 
58
61
  const DEFAULT_HANDLERS = [
@@ -60,6 +63,7 @@ const DEFAULT_HANDLERS = [
60
63
  HANDLERS.PAGINATE,
61
64
  HANDLERS.SHOW,
62
65
  HANDLERS.UPDATE,
66
+ HANDLERS.PATCH,
63
67
  HANDLERS.DELETE,
64
68
  ];
65
69
 
@@ -68,6 +72,7 @@ const HTTP_METHODS = {
68
72
  GET: "GET",
69
73
  PUT: "PUT",
70
74
  DELETE: "DELETE",
75
+ PATCH: "PATCH",
71
76
  };
72
77
 
73
78
  const API_ROUTE_TEMPLATES = {
@@ -79,6 +84,11 @@ const API_ROUTE_TEMPLATES = {
79
84
  url: (prefix, parentUrl, resource) => `/${prefix}/${parentUrl}${resource}`,
80
85
  method: HTTP_METHODS.GET,
81
86
  },
87
+ [HANDLERS.ALL]: {
88
+ url: (prefix, parentUrl, resource) =>
89
+ `/${prefix}/${parentUrl}${resource}/all`,
90
+ method: HTTP_METHODS.GET,
91
+ },
82
92
  [HANDLERS.SHOW]: {
83
93
  url: (prefix, parentUrl, resource, primaryKey) =>
84
94
  `/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
@@ -89,10 +99,10 @@ const API_ROUTE_TEMPLATES = {
89
99
  `/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
90
100
  method: HTTP_METHODS.PUT,
91
101
  },
92
- [HANDLERS.AUTOSAVE]: {
102
+ [HANDLERS.PATCH]: {
93
103
  url: (prefix, parentUrl, resource, primaryKey) =>
94
- `/${prefix}/${parentUrl}${resource}/:${primaryKey}/autosave`,
95
- method: HTTP_METHODS.PUT,
104
+ `/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
105
+ method: HTTP_METHODS.PATCH,
96
106
  },
97
107
  [HANDLERS.DELETE]: {
98
108
  url: (prefix, parentUrl, resource, primaryKey) =>
package/src/core/Model.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import pluralize from "pluralize";
2
2
  import { snakeCase } from "snake-case";
3
- import { RELATIONSHIPS, HANDLERS } from "./../constants.js";
4
- const { INSERT, SHOW, UPDATE, PAGINATE, DELETE } = HANDLERS;
3
+ import { RELATIONSHIPS, DEFAULT_HANDLERS } from "./../constants.js";
5
4
 
6
5
  class Model {
7
6
  constructor() {
@@ -25,7 +24,7 @@ class Model {
25
24
  }
26
25
 
27
26
  get handlers() {
28
- return [INSERT, SHOW, PAGINATE, UPDATE, DELETE];
27
+ return [...DEFAULT_HANDLERS];
29
28
  }
30
29
 
31
30
  get middlewares() {
@@ -0,0 +1,73 @@
1
+ import {
2
+ callHooks,
3
+ getRelatedData,
4
+ filterHiddenFields,
5
+ serializeData,
6
+ addForeignKeyQuery,
7
+ } from "./helpers.js";
8
+ import { HOOK_FUNCTIONS, HANDLERS } from "./../constants.js";
9
+ import QueryParser from "./../core/QueryParser.js";
10
+
11
+ export default async (context) => {
12
+ const { request, response, model, models, trx, relation, parentModel } =
13
+ context;
14
+
15
+ const queryParser = new QueryParser({ model, models });
16
+
17
+ // We should parse URL query string to use as condition in Lucid query
18
+ const conditions = queryParser.get(request.query);
19
+
20
+ // Creating a new database query
21
+ const query = trx.from(model.instance.table);
22
+
23
+ // Users should be able to select some fields to show.
24
+ queryParser.applyFields(query, conditions.fields);
25
+
26
+ // Binding parent id if there is.
27
+ addForeignKeyQuery(request, query, relation, parentModel);
28
+
29
+ // Users should be able to filter records
30
+ queryParser.applyWheres(query, conditions.q);
31
+
32
+ await callHooks(model, HOOK_FUNCTIONS.onBeforeAll, {
33
+ ...context,
34
+ conditions,
35
+ query,
36
+ });
37
+
38
+ // User should be able to select sorting fields and types
39
+ queryParser.applySorting(query, conditions.sort);
40
+
41
+ const result = await query;
42
+
43
+ // We should try to get related data if there is any
44
+ await getRelatedData(
45
+ result.data,
46
+ conditions.with,
47
+ model,
48
+ models,
49
+ trx,
50
+ HANDLERS.ALL,
51
+ request
52
+ );
53
+
54
+ await callHooks(model, HOOK_FUNCTIONS.onAfterAll, {
55
+ ...context,
56
+ result,
57
+ conditions,
58
+ query,
59
+ });
60
+
61
+ // Serializing the data by the model's serialize method
62
+ result.data = await serializeData(
63
+ result.data,
64
+ model.instance.serialize,
65
+ HANDLERS.ALL,
66
+ request
67
+ );
68
+
69
+ // Filtering hidden fields from the response data.
70
+ filterHiddenFields(result.data, model.instance.hiddens);
71
+
72
+ return response.json(result);
73
+ };
@@ -1,8 +1,9 @@
1
- import autosave from "./autosave.js";
1
+ import all from "./all.js";
2
+ import patch from "./patch.js";
2
3
  import store from "./store.js";
3
4
  import show from "./show.js";
4
5
  import paginate from "./paginate.js";
5
6
  import update from "./update.js";
6
7
  import destroy from "./destroy.js";
7
8
 
8
- export default { autosave, store, show, paginate, update, destroy };
9
+ export default { all, patch, store, show, paginate, update, destroy };
@@ -26,15 +26,15 @@ export default async (context) => {
26
26
  // Binding parent id if there is.
27
27
  addForeignKeyQuery(request, query, relation, parentModel);
28
28
 
29
- // Users should be able to filter records
30
- queryParser.applyWheres(query, conditions.q);
31
-
32
29
  await callHooks(model, HOOK_FUNCTIONS.onBeforePaginate, {
33
30
  ...context,
34
31
  conditions,
35
32
  query,
36
33
  });
37
34
 
35
+ // Users should be able to filter records
36
+ queryParser.applyWheres(query, conditions.q);
37
+
38
38
  // User should be able to select sorting fields and types
39
39
  queryParser.applySorting(query, conditions.sort);
40
40
 
@@ -84,7 +84,7 @@ export default async (context) => {
84
84
  item = await serializeData(
85
85
  item,
86
86
  model.instance.serialize,
87
- HANDLERS.AUTOSAVE,
87
+ HANDLERS.PATCH,
88
88
  request
89
89
  );
90
90
 
@@ -26,9 +26,6 @@ export default async (context) => {
26
26
  // If there is a relation, we should bind it
27
27
  addForeignKeyQuery(request, query, relation, parentModel);
28
28
 
29
- // Users should be able to filter records
30
- queryParser.applyWheres(query, conditions.q);
31
-
32
29
  // We should add this condition in here because of performance.
33
30
  query.where(
34
31
  model.instance.primaryKey,
@@ -41,6 +38,9 @@ export default async (context) => {
41
38
  conditions,
42
39
  });
43
40
 
41
+ // Users should be able to filter records
42
+ queryParser.applyWheres(query, conditions.q);
43
+
44
44
  let item = await query.first();
45
45
  if (!item) {
46
46
  throw new HttpResponse(404, {
@@ -43,9 +43,14 @@ export default async (context) => {
43
43
  formData,
44
44
  });
45
45
 
46
- let [insertedPrimaryKeyValue] = await trx(model.instance.table)
46
+ const [returningResult] = await trx(model.instance.table)
47
47
  .insert(formData)
48
- .returning("id");
48
+ .returning(model.instance.primaryKey);
49
+
50
+ let insertedPrimaryKeyValue =
51
+ typeof returningResult === "number"
52
+ ? returningResult
53
+ : returningResult[model.instance.primaryKey];
49
54
 
50
55
  // If the user use a special primary key value, we should use that value
51
56
  if (insertedPrimaryKeyValue === 0) {
@@ -1,6 +1,6 @@
1
1
  import IoC from "./../core/IoC.js";
2
2
  import pluralize from "pluralize";
3
- import { paramCase } from "change-case";
3
+ import { paramCase, camelCase } from "change-case";
4
4
  import { RELATIONSHIPS, API_ROUTE_TEMPLATES } from "./../constants.js";
5
5
  import Handlers from "./../handlers/index.js";
6
6
 
@@ -122,7 +122,7 @@ const createNestedRoutes = async (
122
122
  await createRouteByModel(
123
123
  model,
124
124
  models,
125
- `${urlPrefix}${resource}/:${getPrimaryKeyName(model)}/`,
125
+ `${urlPrefix}${resource}/:${camelCase(relation.foreignKey)}/`,
126
126
  model,
127
127
  relation,
128
128
  false
@@ -164,11 +164,19 @@ const getModelMiddlewares = (model, handler) => {
164
164
  return middlewares;
165
165
  };
166
166
 
167
- const getRootPrefix = () => {
168
- if (Config.Application.prefix) {
169
- return Config.Application.prefix.replace(/^\/|\/$/g, "");
167
+ export const getRootPrefix = async () => {
168
+ const config = await IoC.use("Config");
169
+ let prefix = config?.Application?.prefix || "api";
170
+
171
+ if (prefix.substr(0, 1) === "/") {
172
+ prefix = prefix.substr(1);
173
+ }
174
+
175
+ if (prefix.substr(prefix.length - 1) === "/") {
176
+ prefix = prefix.substr(0, prefix.length - 1);
170
177
  }
171
- return "api";
178
+
179
+ return prefix;
172
180
  };
173
181
 
174
182
  const createRouteByModel = async (
@@ -208,7 +216,7 @@ const createRouteByModel = async (
208
216
 
209
217
  const routeTemplate = API_ROUTE_TEMPLATES[handler];
210
218
  const url = routeTemplate.url(
211
- getRootPrefix(),
219
+ await getRootPrefix(),
212
220
  urlPrefix,
213
221
  resource,
214
222
  model.instance.primaryKey