Elegant and simple way to build requests for REST API
This package helps you quickly to build requests for REST API. Move your logic and backend requests to dedicated classes. Keep your code clean and elegant.
🔥 If you use Laravel, this package matches perfectly with spatie/laravel-query-builder.
Basic usage
Give me the result for a given criteria, include some entities, append extra fields and order the result!
// GET /posts?filter[status]=ACTIVE&include=user,category&append=likes&orderBy=-created_at,category_id let posts = await Post
Just give me the first occurrence from response:
// GET /posts?filter[status]=ACTIVE let post = await Post
Nice! Now I want a specific object:
// GET /posts/1 let post = await Post
Edit this and send it back:
// PUT /posts/1 posttitle = 'Awsome!'post
Ops, delete it!
// DELETE /posts/1 post
Let's create a new object and post it:
let post = title: 'Cool!' // or let post = {}posttitle = 'Another one' // POST /post post
We can use relationships:
// GET /users/1let user = await User // GET users/1/postslet posts = await user
Installation
yarn add vue-api-query
NUXT
Create a plugin ~/plugins/vue-api-query.js
// inject global axios instance as http client to Model { Model$http = ctx$axios}
And register it on nuxt.config.js
plugins: '~plugins/vue-api-query'
VUE
Set up on src/main.js
... // inject global axios instance as http client to ModelModel$http = axios ...
Configuration
Define a base model
Your base model should extend from vue-api-query
Model. Use base models is good practice in order to abstract configurations from your domain models.
models/Model.js
// define a base url for a REST API { return 'http://my-api.com' } // implement a default request method { return this$http }
Define your domain models
Just extends from your base model, implement the resource()
method... and done!
models/User.js
return 'users'
But, if your model does not work with default primary key ('id'),you need to override the primaryKey()
method:
return 'someId'
Of course you can add extra methods and computed properties like this:
// computed properties are reactive -> user.fullname // make sure to use "get" prefix get return ` ` // method -> user.makeBirthday() thisage += 1
You can set up relationships:
{ return this }
It's ok if in some situations you need to call a custom resource from a already defined model. You can override dynamically the default resource calling custom()
method.
// GET /postslet posts = await Post // GET /posts/latestlet latest = await Post
The custom()
method can be called with multiple arguments to build
resource endpoints and hierarchies. Simply supply them in the correct order.
Any combination of strings and models is possible.
let user = id: 1 let post = // GET /users/1/posts/latest const result = await Post
Full example
/models/Post.js
// done :) return 'posts'
/models/User.js
return 'users' { return this } // computed properties :) get return ` ` // methods :) thisage += 1
If the backend responds with ...
// response from API for /users/1 id: 1 firstname: "John" lastname: "Doe" age: 25
We can do this:
//GET /users/1let user = await User console // John Doe useruser
Then save()
method will send back the new payload:
// PUT /users/1 firstname: "John" lastname: "Doe" age: 26 //<--- changed
You also can do that:
//GET /posts?filter[status]=ACTIVE,ARCHIVED let posts = await Post
If you like the "promise way" just do it like this:
// single object let user User // array of objects let users User
And in some page/component:
<template> User: <code> user </code> Posts from user: <code> posts </code></template><script> return user: {} posts: {} async thisuser = await User thisposts = await thisuser </script>
Relationships
// GET /users/1let user = await User // GET /users/1/postslet posts = await user // Yes, you can do that before getting the postslet posts = await user
If you like nested relationships ...
// GET /posts/{id_post}/comments let comments = await thispost // Pick any comment from list and edit it let comment = comments0commenttext = 'Changed!' // PUT /posts/{id_post}/comments/{id_comment} await comment // DELETE /posts/{id_post}/comments/{id_comment} await comment
Creating new related objects is easy. Just use the for()
method, passing the related object.
let post = title: 'Woo!' // POST /posts await post let comment = text: 'New one for this post' // POST /posts/1/comments await comment
The for()
method can take multiple objects to build hierarchy levels.
let user = id: 1 let post = await user // Related objects go in order of their appearance in the URL. let comment = text: 'for() takes multiple objects.' // POST /users/1/posts/1/comments await comment
If you need to get a nested resource, without getting the parent model at first, you can do something like this.
// GET /users/1/posts let User = id: 1let Post = await User // GET /users/1/posts/2let User = id: 1let Post = await User
And just for convenience you can POST or PUT with any payload to backend:
// POST /posts/{id_post}/comments await thisposts // PUT /posts/{id_post}/comments await thisposts
Pagination
// GET /users?sort=firstname&page=1&limit=20 let users = await User
Selecting fields
Just want only some fields?
// GET posts?fields[posts]=title,content let post = await Post
With related entities:
// GET posts?include=user&fields[posts]=title,content&fields[user]=firstname,age let post = await Post
TIP: If you are using spatie/laravel-query-builder, when using related entities, you must pass extra fields:
// GET posts?include=user&fields[posts]=title,content,user_id&fields[user]=id,firstname,age let post = await Post
Custom params
If you need to pass any extra param not provided by vue-api-query
pattern, just use the params()
method while querying:
// GET /users?doSomething=yes&process=no let users = await User
Of course you can chain it with other methods, including on relationships.
// GET /posts/1/comments?include=user&blah=123 let comments = await post
Customize query parameters name
If you need to change default values just override parametersName()
on your Base Model. So, the generated query string will use this new values.
models/Model.js
{ return include: 'include_custom' filter: 'filter_custom' sort: 'sort_custom' fields: 'fields_custom' append: 'append_custom' page: 'page_custom' limit: 'limit_custom' }
Response from backend
This package automatically handles the response from backend and convert it into an instance of a such Model.
Single object
If your backend responds with a single object as a ROOT ELEMENT like this:
id: 1 firstname: 'John' lastname: 'Doe' age: 25
So, find()
and first()
methods automatically will convert the backend response into an instace of User
model.
let user = await User //or let user = await User // will work, because an instance of User was created from response user
This WILL NOT be converted into User
model, because the main data is not the root element.
user: id: 1 firstname: 'John' lastname: 'Doe' age: 25
Array of objects
An array of items from backend would be converted in the same way, ONLY if it responds in these formats:
let user = await User
// works - array of object is the root element id: 1 firstname: 'John' lastname: 'Doe' age: 25 id: 2 firstname: 'Mary' lastname: 'Doe' age: 22
// works - `data` exists in the root and contains the array of objects data: id: 1 firstname: 'John' lastname: 'Doe' age: 25 id: 2 firstname: 'Mary' lastname: 'Doe' age: 22 someField: '' anotherOne: '' // Normally you would handle the response like this let response = Userlet users = responsedata // or like this const data = Userlet users = data // but you can use the "fetch style request" with "$get()" let users = await User
This WILL NOT be converted into an array of User
model.
users: id: 1 firstname: 'John' lastname: 'Doe' age: 25 id: 2 firstname: 'Mary' lastname: 'Doe' age: 22 someField: '' anotherOne: ''
Thanks
-
Inspiration from milroyfraser/sarala.
-
Elegancy from DavidDuwaer/coloquent.
Why another package if we have those? Because currently (march, 2018) they restricted backend response to JSON API specification.
Contact
Twitter @robsontenorio