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.
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
.where('status', 'ACTIVE')
.include('user', 'category')
.append('likes')
.orderBy('-created_at', 'category_id')
.get()
Just give me the first occurrence from response:
// GET /posts?filter[status]=ACTIVE
let post = await Post
.where('status', 'ACTIVE')
.first()
Nice! Now I want a specific object:
// GET /posts/1
let post = await Post.find(1)
Edit this and send it back:
// PUT /posts/1
post.title = 'Awsome!'
post.save()
Ops, delete it!
// DELETE /posts/1
post.delete()
Let's create a new object and post it:
let post = new Post({title: 'Cool!'})
// or
let post = new Post({})
post.title = 'Another one'
// POST /post
post.save()
We can use relationships:
// GET /users/1
let user = await User.find(1)
// GET users/1/posts
let posts = await user
.posts()
.get()
yarn add vue-api-query
Create a plugin ~/plugins/vue-api-query.js
// inject global axios instance as http client to Model
import { Model } from 'vue-api-query'
export default function (ctx, injext) {
Model.$http = ctx.$axios
}
And register it on nuxt.config.js
plugins: [
'~plugins/vue-api-query'
]
Set up on src/main.js
[...]
import axios from 'axios'
import { Model } from 'vue-api-query'
// inject global axios instance as http client to Model
Model.$http = axios
[...]
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
import { Model as BaseModel } from 'vue-api-query'
export default class Model extends BaseModel {
// define a base url for a REST API
baseURL () {
return 'http://my-api.com'
}
// implement a default request method
request (config) {
return this.$http.request(config)
}
}
Just extends from your base model, implement the resource()
method... and done!
models/User.js
import Model from './Model'
export default class User extends Model {
resource()
{
return 'users'
}
}
But, if your model does not work with default primary key ('id'),you need to override the primaryKey()
method:
import Model from './Model'
export default class User extends Model {
primaryKey()
{
return 'someId'
}
}
Of course you can add extra methods and computed properties like this:
import Model from './Model'
export default class User extends Model {
// computed properties are reactive -> user.fullname
// make sure to use "get" prefix
get fullname()
{
return `${this.firstname} ${this.lastname}`
}
// method -> user.makeBirthday()
makeBirthday()
{
this.age += 1
}
}
You can set up relationships:
import Model from './Model'
import Post from './Post'
export default class User extends Model {
posts () {
return this.hasMany(Post)
}
}
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 /posts
let posts = await Post.get()
// GET /posts/latest
let latest = await Post
.custom('posts/latest')
.first()
/models/Post.js
import Model from './Model'
export default class Post extends Model {
// done :)
resource()
{
return 'posts'
}
}
/models/User.js
import Model from './Model'
import Post from './Post'
export default class User extends Model {
resource()
{
return 'users'
}
posts () {
return this.hasMany(Post)
}
// computed properties :)
get fullname()
{
return `${this.firstname} ${this.lastname}`
}
// methods :)
makeBirthday()
{
this.age += 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/1
let user = await User.find(1)
console.log(user.fullname) // John Doe
user.makeBirthday()
user.save()
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
.whereIn('status', ['ACTIVE', 'ARCHIVED'])
.get()
If you like the "promise way" just do it like this:
// single object
let user
User
.where('status', 'ACTIVE')
.first()
.then(response => {
user = response
})
// array of objects
let users
User
.where('status', 'ACTIVE')
.get()
.then(response => {
users = response
// or (depending on backend response)
users = response.data
})
And in some page/component:
<template>
User:
<code>
{{ user }}
</code>
Posts from user:
<code>
{{ posts }}
</code>
</template>
<script>
import User from '@/models/User'
export default {
data()
{
return {
user: {},
posts: {}
}
},
async mounted()
{
this.user = await User.find(1)
this.posts = await this.user.posts().get()
}
}
</script>
// GET /users/1
let user = await User.find(1)
// GET /users/1/posts
let posts = await user.posts().get()
// Yes, you can do that before getting the posts
let posts = await user
.posts()
.where(...)
.append(...)
.include(...)
.orderBy(...)
.get()
If you like nested relationships ...
// GET /posts/{id_post}/comments
let comments = await this.post.comments().get()
// Pick any comment from list and edit it
let comment = comments[0]
comment.text = 'Changed!'
// PUT /posts/{id_post}/comments/{id_comment}
await comment.save()
// DELETE /posts/{id_post}/comments/{id_comment}
await comment.delete()
Creating new related objects is easy. Just use the for()
method, passing the related object.
let post = new Post({title: 'Woo!'})
// POST /posts
await post.save()
let comment = new Comment({text: 'New one for this post'}).for(post)
// POST /posts/1/comments
await comment.save()
The for()
method can take multiple objects to build hierarchy levels.
let user = new User({id: 1})
let post = await user.posts().first()
// Related objects go in order of their appearance in the URL.
let comment = new Comment({text: 'for() takes multiple objects.'}).for(user, post)
// POST /users/1/posts/1/comments
await comment.save()
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 = new User({id: 1})
let Post = await User.posts().get()
// GET /users/1/posts/2
let User = new User({id: 1})
let Post = await User.posts().find(2)
And just for convenience you can POST or PUT with any payload to backend:
// POST /posts/{id_post}/comments
await this.posts.comments().attach(payload)
// PUT /posts/{id_post}/comments
await this.posts.comments().sync(payload)
// GET /users?sort=firstname&page=1&limit=20
let users = await User
.orderBy('firstname')
.page(1)
.limit(20)
.get()
Just want only some fields?
// GET posts?fields[posts]=title,content
let post = await Post
.select(['title', 'content'])
.get()
With related entities:
// GET posts?include=user&fields[posts]=title,content&fields[user]=firstname,age
let post = await Post
.select({
posts: ['title', 'content'],
user: ['age', 'firstname']
})
.include('user')
.get()
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
.select({
posts: ['title', 'content', 'user_id'], //user_id
user: ['id', 'age', 'firstname'] //id
})
.include('user')
.get()
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
.params({
doSomething: 'yes',
process: 'no'
})
.get()
Of course you can chain it with other methods, including on relationships.
// GET /posts/1/comments?include=user&blah=123
let comments = await post
.comments()
.include('user')
.params({blah: 123})
.get()
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
import { Model as BaseModel } from 'vue-api-query'
export default class Model extends BaseModel {
parameterNames () {
return {
include: 'include_custom',
filter: 'filter_custom',
sort: 'sort_custom',
fields: 'fields_custom',
append: 'append_custom',
page: 'page_custom',
limit: 'limit_custom'
}
}
}
This package automatically handles the response from backend and convert it into an instance of a such Model.
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.find(1)
//or
let user = await User.first()
// will work, because an instance of User was created from response
user.makeBirthday()
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
}
An array of items from backend would be converted in the same way, ONLY if it responds in these formats:
let user = await User.get()
// 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 = User.get()
let users = response.data
// or like this
const { data } = User.get()
let users = data
// but you can use the "fetch style request" with "$get()"
let users = await User.$get()
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: '',
}
-
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.
Twitter @robsontenorio