koa-restql

by Meituan-Dianping

Meituan-Dianping / koa-restql

Build real RESTful APIs without writing one line of code.

444 Stars 60 Forks Last release: Not found MIT License 304 Commits 0 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

koa-restql

Travis branch NPM version

Build real RESTful APIs without writing one line of code. Cool, right?

Now it works perfectly with MySQL.

[toc]

Installation

koa-restql requires node v6.0.0 or higher for (partial) ES2015 support.

npm install --save koa-restql

Usage

const koa     = require('koa')
const RestQL  = require('koa-restql')

let app = koa() let restql = new RestQL(sequelize.models) // Build APIs from sequelize.models app.use(restql.routes())

How to request real RESTful APIs

Basic

GET /user

If you just have one database table and sequelize model both named

user
, just choose the right HTTP method to visit path as exactly same name as it.

Using

querystring
in your url can add condition or limit for the request. For more details, read about
querystring
.

List

  • Request

    GET /user
    
  • Response

    HTTP/1.1 206 Partial Content
    X-Range: items 0-2/10
    
    [
      {
        "id": 1,
        "name": "Li Xin"
      },
      {
        "id": 2,
        "name": "Zhang Chi"
      }
    ]
    

    Note:

    • Request for a list will always respond an array.
    • This response example include necessary HTTP headers to explain how
      Partial Content
      works. If the response was just part of the list, the API would like to response HTTP status code 206.

Single

  • Request

    GET /user/1
    
  • Response

    {
      "id": 1,
      "name": "Li Xin"
    }
    

    Note: Request path with id will always respond an object.

Association

1:1

To define an 1:1 association with sequelize, use

model.hasOne()
or
model.belongsTo()
.

  • Request

    GET /user/1/profile
    
  • Response

    {
      "id": 1,
      "user_id": 1,
      "site": "https://github.com/crzidea"
    }
    

    Note: This example is for

    hasOne()
    . If the
    profile
    was an association defined with
    belongTo()
    , there should not be
    user_id
    field.

1:N

To define an 1:N association with sequelize, use

model.belongsTo()
.

List
  • Request

    GET /user/1/messages
    
  • Response

    [
      {
        "id": 1,
        "content": "hello"
      },
      {
        "id": 2,
        "content": "world"
      }
    ]
    
Single
  • Request

    GET /user/1/messages/2
    
  • Response

    {
      "id": 2,
      "content": "world"
    }
    

N:M

To define an N:M association with sequelize, use

model.belongsToMany()
.

Basicly, you can use the same way to request n:n association as 1:N association. The difference is response.

  • Request

    GET /user/1/friends/2
    
  • Response

    {
      "id": 2,
      "name": "Zhang Chi",
      "friendship": {
        "id": 1,
        "user_id": 1,
        "friend_id": 2
      }
    }
    

    Note: RestQL will respond the target model with another model referred

    through
    option.

Another noticeable problem is, you can not do the following query with association path although it is supported by sequelize:

models.user.findAll(
  {
    include: models.user.association.friends
  }
)

But, fortunately, you can implement the query with

querystring
like this:
GET /user?_include%5B0%5D=friends

Read more.

CRUD

RestQL could do all CRUD operations for you. Just choose the right HTTP method to access either the resource or the association path.

Supported HTTP verbs:

HTTP verb

CRUD
GET Read
POST Create
PUT Create/Update
DELETE Delete

Supported HTTP method with body:

HTTP verb

List Single
POST Array/Object ×
PUT Array/Object Object
  • List
    path examples:
    • /resource
    • /resource/:id/association
      , association is
      1:n
      relationship
    • /resource/:id/association
      , association is
      n:m
      relationship
  • Single
    path examples:
    • /resource/:id
    • /resource/:id/association
      , association is
      1:1
      relationship
    • /resource/:id/association/:id
      , association is
      1:n
      relationship
    • /resource/:id/association/:id
      , association is
      n:m
      relationship

Note:

PUT
method must be used with
unique key(s)
, which means you can not use
PUT
method with a request body without an
unique key
.

To use

POST
or
PUT
method, you should put data into request body. Example:
POST /user

{ "name": "Li Xin" }

querystring

It's strongly recommended that use

qs
to stringify nesting

querystring
s. And this document will assume you will use
qs
to stringify querystring from JavaScript object.

Example:

qs.stringify({a: 1, b:2}) // => a=1&b=2

To understand RestQL querystring, there are only 3 rules:

  • Every keys in querystring not start with

    _
    , will be directly used as
    where
    option for
    sequelize#query()
    . Example:
    // query
    {
      name: "Li Xin"
    }
    // option for sequelize
    {
      where: {
        name: "Li Xin"
      }
    }
    
  • Every keys in querystring start with

    _
    , will be directly used as
    sequelize#query()
    .
    // query
    {
      _limit: 10
    }
    // option for sequelize
    {
      limit: 10
    }
    
  • include
    option for
    sequelize#query()
    should be passed as
    String
    of association name.
    // query
    {
      _include: ['friends']
    }
    // option for sequelize
    {
      include: [
        models.user.association.friends
      ]
    }
    

Sometimes, you want modify

query
in your own middleware. To do so, you should modify
this.restql.query
instead of
this.request.query
or
this.query
, because the
query
MUST be parsed with the package
qs
, not
querystring
(which is default package of koa).

Access Control

There are at least 2 ways to implement the

Access Control
:
  1. Add another middleware before request be handled by RestQL.
  2. Add options on
    sequelize#model#associations
    , RestQL will handle the options.

This document will only talk about the 2nd way. And the option was only support with associations, not with models.

  1. To specify which association should not be accessed by RestQL, add

    ignore
    option. Example:
    models.user.hasOne(
      models.privacy,
      {
        restql: {
          ignore: true
        }
      }
    )
    
  2. To specify an association should not be accessed by specific HTTP method, add the method to

    ignore
    as an array element. Example:
    models.user.hasOne(
      models.privacy,
      {
        restql: {
          ignore: ['get']
        }
      }
    )
    

Running tests

npm test

License

MIT

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.