Giter Site home page Giter Site logo

tuksik / meteor-restivus Goto Github PK

View Code? Open in Web Editor NEW

This project forked from kahmali/meteor-restivus

0.0 1.0 0.0 123 KB

ReST APIs for the Best of Us! - A Meteor package for building ReSTful APIs: Inspired by RestStop2 and built on Iron Router.

License: MIT License

CoffeeScript 94.33% JavaScript 5.67%

meteor-restivus's Introduction

Restivus

ReST APIs for the Best of Us!

Restivus makes building ReSTful APIs in Meteor 0.9.0+ an absolute breeze. The package is inspired by RestStop2 and uses Iron Router's server-side routing to provide:

  • A simple interface for building ReSTful APIs
  • User authentication via the API
    • Optional login and logout endpoints
    • Access to this.user in authenticated endpoints
  • NEW! Role permissions for limiting access to specific endpoints
    • Works alongside the alanning:roles package - Meteor's accepted role permission package
  • Coming soon:
    • Easy setup of CRUD endpoints on Meteor Collections

Restivus is still a work in progress. Feature requests are welcome, and can be created and voted on using GitHub Issues!

Installation

You can install Restivus using Meteor's package manager:

> meteor add nimble:restivus

And to update Restivus to the latest version:

> meteor update nimble:restivus

Quick Start

CoffeeScript:
if Meteor.isServer

  # API must be configured and built after startup!
  Meteor.startup ->

    # Global configuration
    Restivus.configure
      useAuth: true

    # Maps to: /api/users
    Restivus.add 'users',
      get: ->
        Meteor.users.find().fetch()
      post:
        authRequired: true
        action: ->
          Accounts.createUser
            email: @bodyParams.email
            password: @bodyParams.password
          Meteor.users.findOne {'emails.address': @bodyParams.email}
      delete:
        roleRequired: ['admin', 'dev']
        action: ->
          if Meteor.users.remove()
            {success: true, message: "All users removed"}
          else
            statusCode: 404
            body: {success: false, message: "No users found"}

    # Maps to: api/friends/abc123
    Restivus.add 'friends/:friendId', {authRequired: true},
      get: ->
        _.findWhere @user.profile.friends, {_id: @urlParams.friendId}
      delete: ->
        if _.contains @user.profile.friends, @urlParams.friendId
          Meteor.users.update(userId, {$pull: {'profile.devices.android': deviceId}})
          {success: true, message: 'Friend removed'}
        else
          statusCode: 404
          body: {success: false, message: 'Friend not found. No friend removed.'}
JavaScript:
if(Meteor.isServer) {

  // API must be configured and built after startup!
  Meteor.startup(function () {

    // Global configuration
    Restivus.configure({
      useAuth: true
    });

    // Maps to: /api/users
    Restivus.add('users', {
      get: function () {
        return Meteor.users.find().fetch();
      },
      post: {
        authRequired: true,
        action: function () {
          Accounts.createUser({
            email: this.bodyParams.email,
            password: this.bodyParams.password
          });
          return Meteor.users.findOne({emails.address: this.bodyParams.email});
        }
      },
      delete: {
        roleRequired: ['admin', 'dev'],
        action: function () {
          if (Meteor.users.remove()) {
            return {success: true, message: "All users removed"};
          }
          else {
            statusCode: 404,
            body: {success: false, message: "No users found"}
          }
        }
      }
    });

    // Maps to: api/friends/abc123
    Restivus.add('friends/:friendId', {authRequired: true}, {
      get: function () {
        return _.findWhere(this.user.profile.friends, {id: this.urlParams.friendId});
      },
      delete: function () {
        if (_.contains(this.user.profile.friends, this.urlParams.friendId) {
          Meteor.users.update(userId, {$pull: {'profile.devices.android': deviceId}});
          return {success: true, message: 'Friend removed'};
        }
        else {
          return {
            statusCode: 404,
            body: {success: false, message: 'Friend not found. No friend removed.'}
        };
      }
    });
  });
}

Table of Contents

Writing A Restivus API

Configuration Options

The following configuration options are available with Restivus.configure:

  • apiPath String
    • Default: 'api/'
    • The base path for your API. If you use 'api' and add a route called 'users', the URL will be https://yoursite.com/api/users/.
  • useAuth Boolean
    • Default: false
    • If true, POST /login and GET /logout endpoints are added to the API. You can access this.user and this.userId in authenticated endpoints.
  • auth Object
    • token String
      • Default: 'services.resume.loginTokens.token'
      • The path to the auth token in the Meteor.user document. This location will be checked for a matching token if one is returned in auth.user().
    • user Function
      • Default: Get user ID and auth token from X-User-Id and X-Auth-Token headers

        function() {
          return {
            userId: this.request.headers['x-user-id'],
            token: this.request.headers['x-auth-token']
          };
        }
      • Provides one of two levels of authentication, depending on the data returned. The context within this function is the endpoint context without this.user and this.userId (well, that's what we're working on here!). Once the user authentication completes successfully, the authenticated user and their ID will be attached to the endpoint context. The two levels of custom authentication and their required return data are:

        • Partial auth
          • userId: The ID of the user being authenticated
          • token: The auth token to be verified
          • If both a userId and token are returned, authentication will succeed if the token exists in the given Meteor.user document at the location specified in auth.token
        • Complete auth
          • user: The fully authenticated Meteor.user
          • This is your chance to completely override the user authentication process. If a user is returned, any userId and token will be ignored, as it's assumed that you have already successfully authenticated the user (by whatever means you deem necessary). The given user is simply attached to the endpoint context, no questions asked.
  • prettyJson Boolean
    • Default: false
    • If true, render formatted JSON in response.
  • onLoggedIn Function
    • Default: undefined
    • A hook that runs once a user has been successfully logged into their account via the /login endpoint. Context is the same as within authenticated endpoints. Any returned data will be added to the response body as data.extra (coming soon).
  • onLoggedOut Function
    • Default: undefined
    • Same as onLoggedIn, but runs once a user has been successfully logged out of their account via the /logout endpoint. Context is the same as within authenticated endpoints. Any returned data will be added to the response body as data.extra (coming soon).

Important! Restivus must be configured from within the Meteor.startup() callback. Check out the Quick Start example.

Defining Routes

Routes are defined using Restivus.add. A route consists of a path and a set of endpoints defined at that path.

Important! Adding routes must also be done from within the Meteor.startup() callback.

Path Structure

The path is the 1st parameter of Restivus.add. You can pass it a string or regex. If you pass it test/path, the full path will be https://yoursite.com/api/test/path.

Paths can have variable parameters. For example, you can create a route to show a post with a specific id. The id is variable depending on the post you want to see such as "/posts/1" or "/posts/2". To declare a named parameter in the path, use the : syntax followed by the parameter name. When a user goes to that url, the actual value of the parameter will be stored as a property on this.urlParams in your endpoint function.

In this example we have a parameter named _id. If we navigate to the /post/5 url in our browser, inside of the GET endpoint function we can get the actual value of the _id from this.urlParams._id. In this case this.urlParams._id => 5.

CoffeeScript:
# Given a url like "/post/5"
Restivus.add '/post/:_id',
  get: ->
    id = @urlParams._id # "5"
JavaScript:
// Given a url "/post/5"
Restivus.add('/post/:_id', {
  get: function () {
    var id = this.urlParams._id; // "5"
  }
});

You can have multiple url parameters. In this example, we have an _id parameter and a commentId parameter. If you navigate to the url /post/5/comments/100 then inside your endpoint function this.params._id => 5 and this.params.commentId => 100.

CoffeeScript:
# Given a url "/post/5/comments/100"
Restivus.add '/post/:_id/comments/:commentId',
  get: ->
    id = @urlParams._id # "5"
    commentId = @urlParams.commentId # "100"
JavaScript:
// Given a url "/post/5/comments/100"
Restivus.add('/post/:_id/comments/:commentId', {
  get: function () {
    var id = this.urlParams._id; // "5"
    var commentId = this.urlParams.commentId; // "100"
  }
});

If there is a query string in the url, you can access that using this.queryParams.

Coffeescript:
# Given the url: "/post/5?q=liked#hash_fragment"
Restivus.add '/post/:_id',
  get: ->
    id = @urlParams._id
    query = @queryParams # query.q -> "liked"
JavaScript:
// Given the url: "/post/5?q=liked#hash_fragment"
Restivus.add('/post/:_id', {
  get: function () {
    var id = this.urlParams._id;
    var query = this.queryParams; // query.q -> "liked"
  }
});

Route Options

The following options are available in Restivus.add (as the 2nd, optional parameter):

  • authRequired
    • Default: false
    • If true, all endpoints on this route will return a 401 if the user is not properly authenticated.
  • roleRequired
    • Default: undefined (no role required)
    • A string or array of strings corresponding to the acceptable user roles for all endpoints on this route (e.g., 'admin', ['admin', 'dev']). Additional role permissions can be defined on specific endpoints. If the authenticated user does not belong to at least one of the accepted roles, a 401 is returned. Since a role cannot be verified without an authenticated user, setting the roleRequired implies authRequired: true, so that option can be omitted without any consequence. For more on setting up roles, check out the alanning:roles package.

Defining Endpoints

The last parameter of Restivus.add is an object with properties corresponding to the supported HTTP methods. At least one method must have an endpoint defined on it. The following endpoints can be defined in Restivus:

  • get
  • post
  • put
  • delete
  • patch

These endpoints can be defined one of two ways. First, you can simply provide a function for each method you want to support at the given path. The corresponding endpoint will be executed when that type of request is made at that path.

Otherwise, for finer-grained control over each endpoint, you can also define each one as an object with the following properties:

  • authRequired
    • Default: false
    • If true, this endpoint will return a 401 if the user is not properly authenticated. Overrides the option of the same name defined on the entire route.
  • roleRequired
    • Default: undefined (no role required)
    • A string or array of strings corresponding to the acceptable user roles for this endpoint (e.g., 'admin', ['admin', 'dev']). These roles will be accepted in addition to any defined over the entire route. If the authenticated user does not belong to at least one of the accepted roles, a 401 is returned. Since a role cannot be verified without an authenticated user, setting the roleRequired implies authRequired: true, so that option can be omitted without any consequence. For more on setting up roles, check out the alanning:roles package.
  • action
    • Default: undefined
    • A function that will be executed when a request is made for the corresponding HTTP method.
CoffeeScript
Restivus.add 'posts', {authRequired: true},
  get:
    authRequired: false
    action: ->
      # GET api/posts
  post: ->
    # POST api/posts
  put: ->
    # PUT api/posts
  patch: ->
    # PATCH api/posts
  delete: ->
    # DELETE api/posts
JavaScript
Restivus.add('posts', {authRequired: true}, {
  get: function () {
    authRequired: false
    action: function () {
      // GET api/posts
    }
  },
  post: function () {
    // POST api/posts
  },
  put: function () {
    // PUT api/posts
  },
  patch: function () {
    // PATCH api/posts
  },
  delete: function () {
    // DELETE api/posts
  }

In the above examples, all the endpoints except the GETs will require authentication.

Endpoint Context

Each endpoint has access to:

  • this.user
    • The authenticated Meteor.user. Only available if useAuth and authRequired are both true. If not, it will be undefined.
  • this.userId
    • The authenticated user's Meteor.userId. Only available if useAuth and authRequired are both true. If not, it will be undefined.
  • this.urlParams
    • Non-optional parameters extracted from the URL. A parameter id on the path posts/:id would be available as this.urlParams.id.
  • this.queryParams
    • Optional query parameters from the URL. Given the url https://yoursite.com/posts?likes=true, this.queryParams.likes => true.
  • this.bodyParams
    • Parameters passed in the request body. Given the request body { "friend": { "name": "Jack" } }, this.bodyParams.friend.name => "Jack".
  • this.request
  • this.response

Response Data

You can return a raw string:

return "That's current!";

A JSON object:

return { json: 'object' };

A raw array:

return [ 'red', 'green', 'blue' ];

Or optionally include a statusCode or headers. At least one must be provided along with the body:

return {
  statusCode: 404,
  headers: {
    'Content-Type': 'text/plain'
  },
  body: {
    success: false,
    message: "There's nothing here!"
  }
};

All responses contain the following defaults, which will be overridden with any provided values:

  • Status code: 200
  • Headers:
    • Content-Type: text/json
    • Access-Control-Allow-Origin: *
      • This is a CORS-compliant header that allows requests to be made to the API from any domain. Without this, requests from within the browser would only be allowed from the same domain the API is hosted on, which is typically not the intended behavior. To prevent this, override it with your domain.

Documenting Your API

What's a ReST API without awesome docs? I'll tell you: absolutely freaking useless. So to fix that, we use and recommend apiDoc. It allows you to generate beautiful and extremely handy API docs from your JavaScript or CoffeeScript comments. It supports other comment styles as well, but we're Meteorites, so who cares? Check it out. Use it.

Consuming A Restivus API

The following uses the above code.

Basic Usage

We can call our POST /posts/:id/comments endpoint the following way. Note the /api/ in the URL (defined with the api_path option above):

curl --data "message=Some message details" http://localhost:3000/api/posts/3/comments

Authenticating

If you have useAuth set to true, you now have a /login endpoint that returns a userId and authToken. You must save these, and include them in subsequent requests.

Note: Make absolute certain you're using HTTPS, otherwise this is insecure. In an ideal world, this should only be done with DDP and SRP, but, alas, this is a ReSTful API.

curl --data "password=testpassword&user=test" http://localhost:3000/api/login/

The response will look something like this, which you must save (for subsequent requests):

{ success: true, authToken: "f2KpRW7KeN9aPmjSZ", userId: fbdpsNf4oHiX79vMJ }

Authenticated Calls

Since this is a RESTful API (and it's meant to be used by non-browsers), you must include the userId and authToken with each request under the following headers:

  • X-User-Id
  • X-Auth-Token
curl --data "userId=fbdpsNf4oHiX79vMJ&authToken=f2KpRW7KeN9aPmjSZ" http://localhost:3000/api/posts/

Or, pass it as a header. This is probably a bit cleaner:

curl -H "X-Auth-Token: f2KpRW7KeN9aPmjSZ" -H "X-User-Id: fbdpsNf4oHiX79vMJ" http://localhost:3000/api/posts/

Thanks

Thanks to the developers over at Differential for RestStop2, where we got our inspiration for this package and stole tons of ideas and code, as well as the Iron Router team for giving us a solid foundation with their server-side routing in Meteor.

Also, thanks to the following projects, which RestStop2 was inspired by:

License

MIT License. See LICENSE for details.

meteor-restivus's People

Contributors

kahmali avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.