npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

rebound-api

v1.0.4

Published

An Express middleware for elegant API creation

Downloads

4

Readme

Hey! This module has been spun off into its own project from the Rebound Seed Project and can be used as a standalone library for API creation. Feel free to use this with or without the rest of Rebound, though we definately recommend checking it out!

Then, in your app.js file:

// Get our dependancies
  var express = require('express');
  var api     = require('rebound-api');
  
// Init Core
  var app = express();
  app.set('port', PORT);
  
  
/******************************************* 
       Additional Middleware Go Here 
*******************************************/
  
  
// Automatically discover API endpoints in `/api` directory. Must be last middleware.
  app.use(api(express));

// Start Server
  http.createServer(app).listen(app.get('port'), function(){
    console.log(('Express server listening on port ' + app.get('port')));
  });

Its quite simple really – the Rebound API middleware checks req.xhr:

If the request is an AJAX request, it will attempt to roue to an API endpoint defined in your project's /api directory and send a JSON response back to the client.

If the request is not an AJAX request, it will respond using a file that you specify (defaults to /index.html).

Important: This middleware will catch all requests – both AJAX and otherwise – so it must be the last middleware in your express server.

1) API Discovery

When starting, the Rebound API middleware will look for a directory called /api at the root of your project. This directory contains all the files that will define your api (how that works is described below), the paths of which define your public facing API paths.

The project on the left in the below image, will show its corrosponding output when you start your server. This example will be referred to throughout this section:

api_screenshots

API File Paths:

The files paths in your /api directory are the express routes you would normally write at the bottom of your app.js file to handle requests. Here is the express router documentaiton if you need to brush up on how to write routes.

The file and directory names may be any valid string or string pattern used to describe an Express route. For example: /user/:uid?.js, as is shown in the above example, defines a route user that can accept an optional uid (User ID) parameter.

The file name index.js is special. Files named index.js will act as the root route for their parent directory. The donate directory in the above example shows this well. the directory structure:

api
 |--- donate
        |--- history.js
        |--- index.js

Defines two routes: /donate/history and /donate. An equivelent, but far less convenient, structure would be:

api
 |---donate
 |     |--- history.js
 |
 |---donate.js
API Path Specificity:

No more manually managing your route ordering! Your routes are automagically registered with express in order from most to least specific. For instance, above, the user paths are loaded in order from most to least specific: /user/password/recovery > /user/login > /user/:uid?.

API Errors

The Rebound API middleware will display the paths discovered in your console for your debugging pleasure. If there is an error in one of your API files, it will not kill your server. Instead, it will print a nice big, red error for that route along with the error and line number that caused it. Convenient!

2) Writing API Files

We will be using jSend, a simple (and personal favorite) JSON format for sending data back and forth between the server and frontend, in these examples. Feel free to use whatever specification you like best!

The files in your /api folder export the different http methods implemented for this path. The methods implemented for a particular path are printed out next to the registered path in the console, as shown in section 1.

A simple API file may look like this:

// This file implements the `GET` HTTP method, which returns a JSON blob. 
// This JSON is sent back to the client and the response is closed. 
exports.GET = function(req, res){
  return {
    status: 'success',
    data: {
      firstName: 'Luke',
      lastName: 'Skywalker'
    }
};

For you lazy people out there – a tl;dr:

  • These HTTP method implementations are middleware.
  • Different HTTP methods are named exports from your API file. Current valid methods are ALL, GET, POST, UPDATE and DELETE.
  • Like any other middleware, they are passed the req and res objects.
  • These API definitions do not accept a next callback – they are always the last middleware before a response.
  • These middleware should always return either JSON or a Promise.
  • The response value will be sent back to the client.
  • If the response is a Promise, the Rebound API will wait for it to resolve and send its value.
  • If the response JSON has the property code, it will be use as the HTTP status code of the response.
  • If an error occurs in your API call, it will be:
  • Gracefully caught
  • Logged in the console
  • And 500 response will be sent back to the client
  • If no route is found that matches the request, a 400 response is sent back to the client

The full explaination: An API file that only exports a single function will default to the GET http method:

// Same as the example above
module.exports = function(req, res){
  return {
    status: 'success',
    data: {
      firstName: 'Luke',
      lastName: 'Skywalker'
    }
  };
};

An API file that may export multiple HTTP method names:

module.GET = function(req, res){
 // Make a database call or something here
  return {
    status: 'success',
    data: {
      firstName: 'Luke',
      lastName: 'Skywalker'
    }
  };
};

module.POST = function(req, res){
 // Update your database or something here
  return { status: 'success' };
};

An API method implementation may return a Promise. Rebound API will wait for the promise to resolve or reject and sent its resolved or rejected value back to the client. Great for asynchronous database calls:

var Promise = require("bluebird");

module.GET = function(req, res){
 // Make an async database call or something here
  return new Promise(function(resolve, reject){
    resolve({
      status: 'success',
      data: {
        firstName: 'Luke',
        lastName: 'Skywalker'
      }
    });
  });
};

module.POST = function(req, res){
 // Do some async validation or something here
  return new Promise(function(resolve, reject){
    reject({
      status: 'error',
      code: '403',  // If the response has a property `code`, it will be used as the http response code.
      message: 'You are not authorized to post to this endpoint!'
    });
  });
};

3) Calling APIs Server Side

The Rebound API middleware puts an api property on on the res.locals object at the begining of every new request and is accessable to every middleware in your express app. It exposes get, post, put and delete methods which each take a url and optional data object. This allows you to consume your API calls server side to build more powerful services, as well as client side.

A server side call looks like this:

res.locals.api.get('/user/123')
   .then(function(result){
     // You have access to the API result here
   });

An internal API call will always return a Promise, regardless of if the API function returns a Promise itself, or just a JSON blob.

You are able to pass internal API calls an optional JSON object as a second paramater. This object will augment the req.body object on the original request object for the lifetime of the internal API call.

Our API file /api/user/:uid.js

exports.POST = function(req, res){
  // req.body == { firstName: 'Ash', numPkmn: 6 }
  return { status: 'success', data: { yell: 'Gotta Catch 'Em All!' }}
}

Middleware posting to /user/:uid

function(req, res){
  // req.body = { numPkmn: 6 }
  res.locals.api.post('/user/123', { firstName: 'Ash' })
   .then(function(result){
    // You have access to the API result here
    // result.data == { yell: 'Gotta Catch 'Em All!' }
    // req.body == { numPkmn: 6 }
   });
}

The fact that internal APIs always return a promise allows us to do some creative things when drafting other API calls, consuming existing APIs to create new ones.

An API file /api/profile/:uid.js. This endpoint returns an entire profile object. So much info!

exports.GET = function(req, res){
  return { 
    status: 'success', 
    data: { 
      firstName: 'Ash',
      lastName: 'Ketchum',
      numPkmn: 151,
      hometown: 'Pallet Town'
    }
  }
}

An API file /api/miniprofile/:uid.js. This miniprofile API endpoint will only return the firstName and lastName properties of the full profile.

exports.GET = function(req, res){
  res.locals.api.get('/user/' + req.params.uid)
   .then(function(result){
      result.data = {
          firstName: result.data.firstName,
          lastName: result.data.lastName
        };
      return result;
   });
}