CRUDly
Promise based HTTP request library for browser and node.js.
CRUDly gives you a gate object to manage all your API routes.
Wrapper on Axios
Features
- Make XMLHttpRequests from the browser
- Make http requests from node.js
- Supports the Promise API
- Intercept request and response
- Transform request and response data
- Automatic transforms for JSON data
- Client side support for protecting against XSRF
- Autogenerate action names
- Categorizing actions in controllers
- Default actions for controllers to ignore redundancy
Installing
Using npm:
$ npm install CRUDly --save
Using yarn:
$ yarn add CRUDly
Using cdn:
<!-- for production-->
<script src="https://unpkg.com/crudly/dist/crudly.min.js"></script>
<!-- for development-->
<script src="https://unpkg.com/crudly/dist/crudly.js"></script>
CRUDly in a nutshell
The crudlyObj
in the example below is the object that the CRUDly created for you. It has users
controller with some actions (get,create) and auth
controller. It also has its own action (getSiteSetting).
CRUDly gives you a gate object to manage all your API routes.
All you have to do is pass a valid config object to the crudly
function:
const crudly = require('crudly');
const config = {
actions: [{ type: 'get', url: '/setting', name: 'getSiteSetting' }], // route: your-website/setting
controllers: [
{
name: 'users',
actions: [
{ type: 'get', url: '/:id' }, // method: get route: your-website/users/:id
{ type: 'post' }, // method: post route: your-website/users
{ type: 'delete', url: '/:id' } // method: delete route: your-website/users/:id
]
},
{
name: 'auth',
actions: [{ type: 'post', name: 'login' }] // method: post route: your-website/auth
}
]
};
// create your gate like this
const crudlyObj = crudly(config);
And then nice centralized gate object:
// method: get route: your-website/users/:id
let user = await crudlyObj.users.get(123);
// method: post route: your-website/auth
let token = await crudlyObj.auth.login({ username: 'milawd', pass: '123456' });
// method: post route: your-website/users
let response = await crudlyObj.users.create({ username: 'test', pass: '123' });
// method: delete route: your-website/users/:id
await crudlyObj.users.delete(123);
// method: post route: your-website/setting
let siteSetting = await crudlyObj.getSiteSetting();
Example
Maybe you want to send http request to an api like this to get the user whose id is 123.
{ "url": "/api/v1/users/123", "method": "get" }
Or maybe an api like this to create new user.
{ "url": "/api/v1/users", "method": "post" }
Also an api to delete the user whose id is 123.
{ "url": "/api/v1/users?id=123", "method": "delete" }
With CRUDly you just need to create your own gate to handle these URLs as well. See the examples bellow:
//it will take care of your request and get the user back
let user = await gate.users.get(123);
//it will create the new user and get the response back
let response = await gate.users.create({ username: 'test', pass: '123' });
//easily remove the user
await gate.users.delete(123);
NOTE:
async/await
is a part of ECMAScript 2017 and is not supported in Internet Explorer and older browsers, so use with caution.
Create CRUDly gate
All you have to do is pass valid config object to the crudly function:
// in nodejs
const crudly = require('crudly');
// in es6 module
import crudly from 'crudly';
const gate = crudly(config);
How to create config file
- Consider you just have 3 simple APIs. Best way to create your gate is to pass an array of the actions you want.
const crudly = require('crudly');
const config = [
{ type: 'post', url: '/api/v1/users' },
{ type: 'put', url: '/api/v1/users' },
{ type: 'delete', url: '/api/v1/users/:id' }
];
// create your gate like this
const gate = crudly(config);
// then you have gate with 3 actions
gate.create({ username: 'test', pass: '123' })
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.log(error);
});
gate.update({ id: 1234, username: 'test1', pass: '1234' })
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.log(error);
});
gate.delete(1234)
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.log(error);
});
You can see it helps you to call your APIs easier and you don’t have to remember each API route and method.
NOTE: If you are confiused about the function names see Methods map table.
- The next way to create your gate is to pass a valid config object (recommended). It will give you more futures and options to work aroud. For example, you can specifiy the route of your API or set Default actions. Let’s see:
const crudly = require('crudly');
const config = {
root: '/api/v1', //root of your api. default is '/'
defaultActionsConfig: {
timeout: 1000 //each action will have this property
},
// this is actions that will be in te gate object
actions: [{ type: 'post', url: '/users/users' }, { type: 'put', url: '/users/users' }],
controllers: [
{
//this will create users object in the gate
//and actions associated with it
name: 'users',
url: '/users',
actions: [
{ type: 'post', timeout: 2000 }, //also you can change default config
{ type: 'put', loadDefaultConfig: false }, //or ignore the default config
{
type: 'delete',
url: '/:id'
}
]
},
{
name: 'posts',
actions: [
{ type: 'post', timeout: 2000 },
{ type: 'put', loadDefaultConfig: false },
{
type: 'delete',
url: '/:id'
},
{ type: 'get', params: ['id'] }, //posts?id=123123
{ type: 'patch' }
]
}
]
};
// create your gate like this
const gate = crudly(config);
// then you have gate with 3 actions
gate.create({ username: 'test1', pass: '1234' })
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.log(error);
});
gate.users
.create({ username: 'test2', pass: '4321' })
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.log(error);
});
gate.posts
.delete('postid')
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.log(error);
});
gate.users
.delete(1234)
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.log(error);
});
//... and so on
You can read about config properties here: Gate config fields
It was Simple and flexible. Also, you can see the delete, put and post actions are the same in both controllers. For making the config object better and easier you can set Default actions array and both controllers will have thease actions.
Default actions
Lets make the above example better. defaultActions
is an array that will add its elements to the controllers.
The config can be changed to this object:
const config = {
root: '/api/v1',
defaultActionsConfig: {
timeout: 1000
},
actions: [{ type: 'post', url: '/users/users' }, { type: 'put', url: '/users/users' }],
defaultActions: [
{ type: 'post', timeout: 2000 },
{ type: 'put', loadDefaultConfig: false },
{
type: 'delete',
url: '/:id'
}
],
controllers: [
{
name: 'users',
url: '/users',
actions: []
},
{
name: 'posts',
actions: [
{ type: 'get', params: ['id'] }, //posts?id=123123
{ type: 'patch' }
]
}
]
};
It will be the same as the previous config file. You can see the users
controller action’s is empty, but it has the delete action. And in the post controller you just have to add two more missing actions.
problem: But if you want one more controller and the actions are all different from these two controllers?
Consider setting controller and it just has the getSetting action.
The above config wont be changed, you just have to add this controller like this:
const config = {
//...
controllers: [
{
name: 'users',
url: '/users',
actions: []
},
{
name: 'posts',
actions: [
{ type: 'get', params: ['id'] }, //posts?id=123123
{ type: 'patch' }
]
},
{
name: 'setting',
url: '/setting',
loadDefaults: false,
actions: [{ type: 'get', name: 'getSetting', url: '/getSetting' }]
}
]
};
loadDefaults
property will help you to fix this problem.
Interceptors
We have four events that will call in the request lifecycle.
lifecycle | name | info |
---|---|---|
1 | beforeAny |
It will be called before any request was sent to server |
2 | beforeEach |
Before each request, it will be called |
3 | afterEach |
After each request, it will be called |
4 | afterAll |
It will be called if all pending requests are done |
You can set all these events on the gate object.
beforeAny
gate.beforeAny(() => {
console.log('before any request');
});
beforeEach
Here you can check if request URL is valid or not.
gate.beforeEach(request => {
if (!request.url.startsWith('somthing_valid')) throw new Error('url is not valid');
});
Or change request properties.
gate.beforeEach(request => {
if (!request.method == 'get')
request.setProperty('headers', { 'content-type': 'application/json' });
});
afterEach
After each request, you can check the result to be valid or maybe you just want a part of the response. You just have to return part of the response.
gate.afterEach(response => {
if (response.ok) return response.data;
});
Or maybe transform the result.
gate.afterEach(response => {
if (response.ok) return response.blob();
});
afterAll
gate.afterAll(() => {
console.log('after all requests');
});
Gate
Gate config fields
The controllers
field is the minimum gate config field.
fields with
?
are unnecessary.
field | type | info |
---|---|---|
controllers | Array | List of Controller configs |
root? | String | Base route of the API. Default is / |
defaultActionsConfig? | Object | Default Action configs fields that will be merged in each action |
defaultActions? | Array | List of default Action configs that will be added to each controller |
Gate functions
You can check if there is uncompleted request by isRequestPending method.
if (!gate.isRequestPending()) {
console.log('there is no pending request');
}
Add new action to the gate object:
//add new action
gate.addAction({ type: 'post', name: 'testAddAction', url: '/api/v1/users' });
//then use
const res = await gate.testAddAction({ username: 'test', pass: '123' });
Add new controller to the gate object:
//add new controller
gate.addController({ name: 'companies', url: '/companies', actions: [{ type: 'post', url: '/' ]});
//then use
const res = await gate.companies.create({ companyName: 'test', postCode: '123 1234' });
//it also will take care of the default actions
//so if you were added some default actions before you can use it now
const res = await gate.companies.delete(123456);
If you want to merge requests:
const [createRes, user] = await gate.all([
gate.companies.create({ companyName: 'test', postCode: '123 1234' }),
gate.users.get(123123)
]);
Controller
Controller config fields
The name
field is the minimum controller config field. The actions can be null because the list also fill with Default actions.
fields with
?
are unnecessary.
field | type | info |
---|---|---|
name | String | It will be controller object name |
actions? | Array | List of Action configs |
url? | String | Controller url. If it not specified it will be /{controller name} |
loadDefaults? | Boolean | If true it will load the Default actions. Default is true |
Action
Action config fields
The type
and url
fields are the minimum action config fields.
fields with
?
are unnecessary.
field | type | info |
---|---|---|
type | String | Method type (get , put , post , delete , patch , head ) |
url | String | Action url. In internal calls it Always start with / |
name? | String | It will be gate or controller function name |
params? | Array | Array of the action url query params |
headers? | Object | Headers are custom headers to be sent |
timeout? | Number | Specifies the number of milliseconds before the request times out |
proxy? | Object | Defines the hostname and port of the proxy server |
loadDefaultConfig? | Boolean | Merge config to default action config. Default is true |
auth? | Object | Indicates that HTTP Basic auth should be used |
responseType? | String | Indicates the type of data that the server will respond with |
responseEncoding? | String | Indicates encoding to use for decoding responses |
xsrfHeaderName? | String | The name of the http header that carries the xsrf token value |
maxContentLength? | Number | Defines the max size of the http response content in bytes allowed |
maxRedirects? | Number | Defines the maximum number of redirects to follow in node.js |
xsrfCookieName? | String | The name of the cookie to use as a value for xsrf token |
NOTE: for more information about fields you an see axios github page.
Methods map table
You may see untitled actions in the config which also have names. You can see the post method will get create
name.
This is the complete table of the methods and default names.
method | name |
---|---|
get | get |
put | update |
post | create |
delete | delete |
patch | patch |
head | head |
NOTE: You can always pass the name in the action config to ignore default names.
Caution: Names must be uniqe in each section (controller or gate).
Request
The request object contains these functions
name | params | return | description |
---|---|---|---|
getUrl | [] | string |
Get the url of the request |
getBody | [] | Object |
Get the body of the request |
getResponse | [] | Object |
If the request is complited it will returns the response object |
setProperty | [propertyName,Value] | string |
It can change or set the propery of the request |
example:
gate.beforeEach(request => {
if (!request.method == 'get')
request.setProperty('headers', { 'content-type': 'application/json' });
});
//or
gate.beforeEach(request => {
if (!request.method == 'patch') request.setProperty('method', 'post');
});
//...
Response
The response object contains these functions.
name | params | return | description |
---|---|---|---|
blob | [] | Promise<ArrayBuffer> |
Returns promise that it will resolve the data as a array buffer |
text | [] | Promise<string> |
Returns promise that it will resolve the data as a string |
json | [] | Promise<any> |
Returns promise that it will resolve the data as javascript object |
example:
gate.photos
.get(1234)
.then(function(response) {
response.blob();
})
.then(function(arrayBuffer) {
console.log(arrayBuffer);
})
.catch(function(error) {
console.log(error);
});
gate.users
.get(1234)
.then(function(response) {
response.json();
})
.then(function(user) {
console.log(user);
})
.catch(function(error) {
console.log(error);
});
//...
Axios functions
You can also have Axios functions like get, put, post… in the gate object. (we don’t recommend this because the core concept of the crudly is to ignore these functions)
const data = await gate.statics.get('http://localhost/api/v1/posts?id=123');
//or
const res = await gate.statics.post('http://localhost/api/v1/users', { testData: 1 });
//...
list of axios functions
method | name |
---|---|
get | get |
put | put |
post | post |
delete | delete |
patch | patch |
head | head |
Resources
Credits
CRUDly is a usefull wrapper on the powerfull library axios. This is just the beginning of our work and we are going to add new things to it if you help our community and if we succeed to attract attention, it will progress.
License
MIT