Quantcast
Channel: Blog – Stormpath User Identity API
Viewing all articles
Browse latest Browse all 278

Build An API Service in Node.js with Stormpath, Twilio and Stripe

$
0
0

BTC SMS Intro

Building a full-fledged API service isn’t as hard as you may think. By taking advantage of some really useful API services and open source libraries, you can rapidly develop an API service in an incredibly short amount of time!

In this article, I’m going to walk you through the process of building an API service that uses SMS to keep you up-to-date with current value of Bitcoin: Bitcoin SMS!

This API service:

  • Lets users sign up and register for your site.
  • Verifies new user accounts by sending them an email and making them click a link.
  • Generates API keys for developers automatically.
  • Bills developers on a per-API call basis.
  • Handles credit card billing with Stripe.
  • Sends SMS messages via Twilio.
  • Finds Bitcoin exchange rates via Bitcoin Charts.
  • Stores user account data securely with Stormpath.

If you’re at all interested in building API services, or API companies — this article is meant specifically for you!

NOTE: Prefer video over a long article? If so, you can actually watch my screencast where I cover the same things in video form on Youtube here: https://www.youtube.com/watch?v=THDPG2gH7o0

ALSO: all of the code we’ll be writing here can be found on Github here: https://github.com/rdegges/btc-sms

What We’re Building: BitCoin SMS API Service

BTC SMS Demo

What we’re going to build today is a publicly available API service called BTC SMS which allows developers to make API calls that send SMS messages to specified phone numbers with the current value of Bitcoin.

The idea is that a lot of people might want to know the value of Bitcoin at a specific point in time — in order to buy or sell their Bitcoin — so this API service makes that information easy to access by sending SMS messages to a user with the current value of Bitcoin.

The API service we’re building will allow developers to make requests like this:

POST /api/message
{
  "phoneNumber": "+18182223333"
}

In the above example, this request would kick off an SMS message to +18182223333 that says something along the lines of “1 Bitcoin is currently worth $525.00 USD.”

Here’s how it’ll look:

BTC SMS API Call

Now, since we’re actually building an entire API service here — and not just a plain old API, we’ll also be charging money! We’ll be charging developers 2 cents per successful API request in order to cover our costs, and make a little bit of profit =)

So, now that we’ve briefly discussed what we’re going to be making — let’s actually make it!

Set Up Twilio, Stripe and Stormpath For Your API

To get started, you’ll need to create some accounts that we’ll be using for the rest of this guide.

Twilio for SMS

First, you’ll want to go and create an account with Twilio. Twilio is an API service that lets you do all things telephony related: send and receive calls and SMS messages.

For the purposes of the application we’re building, we’ll only be using Twilio to send SMS messages, but there’s a lot more it can do.

Signing up for a Twilio account is free, but if you want to actually follow through with the rest of this article, you’ll need to purchase a phone number you can send SMS messages from — and this typically costs $1 USD per month + 1c per SMS message you send.

Here’s what you’ll want to do:

  • Sign up.
  • Visit the Numbers Page and click “Buy a number”.
  • Purchase any phone number that is SMS ready:

Twilio Buy a Number

  • Now that you have a phone number, write down that phone number, you’ll need it later.
  • Next, go to your Account Page and copy your API key credentials so you can use them later on. Twilio gives you two API tokens: an “AccountSID” and an “AuthToken”. Both of these are necessary to have. Make sure these stay private.

Twilio API Credentials

Stripe for Payments

Next, let’s create a Stripe account. Stripe is a payments provider that allows you accept Credit Cards, Debit Cards, and even Bitcoin as payment options on your site.

Since this demo application will be charging users money for the amount of API calls they make, this is necessary.

Once you’ve created your Stripe account, you’ll want to set your Stripe dashboard to TEST mode — this lets you view the development mode sandbox where you can use Stripe like normal, but with fake credit cards and money:

Stripe Test Dashboard

Next, you’ll want to visit your Stripe API Keys page to view your Stripe API keys. The ones you’ll want to use for testing are the Test Secret Key and Test Publishable Key values:

Stripe API Keys Image

Be sure to take note of both those API key values — we’ll be needing those later.

Stormpath for Authentication and API Key Management

Now that we’ve got both Twilio and Stripe setup, go create a Stormpath account. Stormpath is a free API service that lets you store user accounts and user data. It makes doing stuff like signing users up for your site, managing users, and doing things like password reset and authorization really easy.

Instead of needing to run a database to store your user data in, we can use Stormpath to simplify and speed up the process. It also comes with some nice pre-built login / registration / password reset pages that make things really nice.

Once you’ve signed up for Stormpath, you’ll want to create a new API key and download it locally:

Stormpath Provision API Key

When you generate a new Stormpath API key, you’ll automatically download an apiKey.properties file — this contains your API key information. This file contains two values: an API Key ID and an API Key Secret — we’ll need both of these later.

Next, you’ll need to create a new Stormpath Application. Generally, you’ll want to create one Application per project you work on — since we’re building this BTC SMS project, we’ll create an Application called BTC SMS:

Stormpath Create Application

After creating your Application, be sure to copy down the REST URL link — we’ll be needing this later on to reference our Application when we start coding =)

Bootstrapping an Express.js Application

Now that we’ve gotten all of our required API service accounts setup and configured properly, we’re ready to start writing some code!

The first thing we’ll need to do is create a minimal Express.js application that we can use as we move forward.

Here’s the files / folders we’ll be creating, along with a brief description of what each of them contains:

btc-sms
├── bower.json
├── index.js
├── package.json
├── routes
│   ├── api.js
│   ├── private.js
│   └── public.js
├── static
│   └── css
│       └── main.css
└── views
    ├── base.jade
    ├── dashboard.jade
    ├── docs.jade
    ├── index.jade
    └── pricing.jade

4 directories, 12 files
  • btc-sms: This is the project folder we’ll use to hold all of our code.
  • bower.json: This is the bower package file which contains our front-end dependencies. If you haven’t heard of bower, it’s a package manager for front-end libraries like Twitter Bootstrap.
  • index.js: This is the main ‘entrypoint’ of our Node application. This is where our Express web app is defined, and our middlewares are configured.
  • package.json: This is our Node package file — this contains our library dependencies, and packaging information.
  • routes: This folder holds all of our route code (this is the code that actually makes stuff happen).
  • routes/api.js: This file contains our API code.
  • routes/private.js: This file contains the code that renders our private pages (the customer dashboard).
  • routes/public.js: This file contains our public pages code.
  • static: This folder holds all of our static assets: CSS, Javascript, etc.
  • static/css/main.css: This is our CSS file to make things look decent.
  • views: This folder holds our template code.
  • views/base.jade: This file holds our base template code. This holds the basic HTML page outlines, navbar, and stuff like that.
  • views/dashboard.jade: This file holds our private dashboard page code. This includes some billing logic.
  • views/docs.jade: This file holds our public API documentation.
  • views/index.jade: This is our main home page.
  • views/pricing.jade: This is our public pricing page.

The Views

Now that we’ve seen what our app looks like at a structural level, let’s take a look at the views.

Taking a look at the views first will give you a good understanding of how the site looks / functions before digging into the backend code.

base.jade

The base.jade view contains a page outline and navbar that all pages of the site use. This lets us build a ‘modular’ website with regards to the front-end of the website:

block vars

doctype html
html(lang='en')
  head
    meta(charset='utf-8')
    meta(http-equiv='X-UA-Compatible', content='IE=edge')
    meta(name='viewport', content='width=device-width, initial-scale=1')
    title #{siteTitle} - #{title}
    link(href='/static/bootswatch/sandstone/bootstrap.min.css', rel='stylesheet')
    link(href='/static/css/main.css', rel='stylesheet')
    <!--[if lt IE 9]>
    script(src='/static/html5shiv/dist/html5shiv.min.js')
    script(src='/static/respond/dest/respond.min.js')
    <![endif]-->
  body
    nav.navbar.navbar-default.navbar-static-top
      - var nav = {}; nav[title] = 'active'
      .container
        .navbar-header
          button.navbar-toggle.collapsed(type='button', data-toggle='collapse', data-target='#navbar-collapse')
            span.sr-only Toggle navigation
            span.icon-bar
            span.icon-bar
            span.icon-bar
          a.navbar-brand(href='/') #{siteTitle}
        #navbar-collapse.collapse.navbar-collapse
          ul.nav.navbar-nav
            li(class='#{nav.Home}')
              a(href='/') Home
            li(class='#{nav.Pricing}')
              a(href='/pricing') Pricing
            li(class='#{nav.Docs}')
              a(href='/docs') Docs
            if user
              li(class='#{nav.Dashboard}')
                a(href='/dashboard') Dashboard
              li(class='#{nav.Logout}')
                a(href='/logout') Logout
            else
              li(class='#{nav.Login}')
                a(href='/login') Login
              li(class='#{nav.Register} create-account')
                a(href='/register') Create Account
    block body
    script(src='/static/jquery/dist/jquery.min.js')
    script(src='/static/bootstrap/dist/js/bootstrap.min.js')

Some important things to take note of:

  • We are using Twitter bootstrap for design. This lets us build a well structured site without doing a lot of work.
  • We are using a Bootswatch theme for Bootstrap to make the site look a little bit better than your normal Bootstrap site. The theme we’re using simply overrides some of the basic Bootstrap styles.
  • At the top of our template, there is a block called vars — this lets us define template-specific variables later on.

index.jade

Our index.jade template renders the home page of our site — it’s just a simple static page:

extends base

block vars
  - var title = 'Home'

block body
  .container.index
    h1.text-center Get BTC Rates via SMS
    .row
      .col-xs-12.col-md-offset-2.col-md-8
        .jumbotron.text-justify
          p.
            #{siteTitle} makes it easy to track the value of Bitcoin via SMS.
            Each time you hit the API service, we'll SMS you the current Bitcoin
            price in a user-friendly way.
          a(href='/register')
            button.btn.btn-lg.btn-primary.center-block(type='button') Get Started!

You’ll notice that our Get Started! button is linking to a registration page — this registration page is generated automatically by the Stormpath library that you’ll see later on.

docs.jade

The docs.jade template is just a static page that contains API documentation for developers visiting the site:

extends base

block vars
  - var title = 'Docs'

block body
  .container.docs
    h1.text-center API Documentation
    .row
      .col-xs-12.col-md-offset-2.col-md-8
        p.text-justify
          i.
            This page contains the documentation for this API service.  There is
            only a single API endpoint available right now, so this document is
            fairly short.
        p.text-justify
          i.
            Questions? Please email <a href="mailto:support@apiservice.com">support@apiservice.com</a>
            for help!
        h2 REST Endpoints
        h3 POST /api/message
        span Description
        p.description.
          This API endpoint takes in a phone number, and sends this phone an
          SMS message with the current Bitcoin exchange rate.
        span Input
        .table-box
          table.table.table-bordered
            thead
              tr
                th Field
                th Type
                th Required
            tbody
              tr
                td phoneNumber
                td String
                td true
        span Success Output
        .table-box
          table.table.table-bordered
            thead
              tr
                th Field
                th Type
                th Example
            tbody
              tr
                td phoneNumber
                td String
                td "+18182223333"
              tr
                td message
                td String
                td "1 Bitcoin is currently worth $225.42 USD."
              tr
                td cost
                td Integer
                td #{costPerQuery}
        span Failure Output
        .table-box
          table.table.table-bordered
            thead
              tr
                th Field
                th Type
                th Example
            tbody
              tr
                td error
                td String
                td "We couldn't send the SMS message. Try again soon!"
        span Example Request
        pre.
          $ curl -X POST \
              --user 'id:secret' \
              --data '{"phoneNumber": "+18182223333"}' \
              -H 'Content-Type: application/json' \
              'http://apiservice.com/api/message'

pricing.jade

Like our docs page — the pricing.jade page is just a static page that tells users how much our service costs to use:

extends base

block vars
  - var title = 'Pricing'

block body
  .container.pricing
    h1.text-center Pricing
    .row
      .col-xs-offset-2.col-xs-8.col-md-offset-4.col-md-4.price-box.text-center
        h2 #{costPerQuery}&cent; / query
        p.text-justify.
          We believe in simple pricing.  Everyone pays the same usage-based
          feeds regardless of size.
        p.text-justify.end.
          <i>Regardless of how many requests you make, BTC exchange rates are
          updated once per hour.</i>
    .row
      .col-xs-offset-2.col-xs-8.col-md-offset-4.col-md-4
        a(href='/register')
          button.btn.btn-lg.btn-primary.center-block(type='button') Get Started!

dashboard.jade

The dashboard.jade file is where users land once they’ve either created or logged into an account.

This page does a few things:

  • Displays the user’s API keys.
  • Displays the user’s account balance, and lets users deposit money into their account.
  • Displays the user’s usage information (how many API requests has this user made?).

The way we’re accepting billing information on this page is via the Stripe Checkout Button. To learn more about how this works, you can visit the Stripe site.

What happens is essentially this: if a user clicks the Stripe button, a Javascript popup will appear to collect the user’s payment information.

When the user is done entering their information, this credit card info will be validated by Stripe, and a unique token will be generated to allow us to bill this user later on.

Here’s the dashboard code:

extends base

block vars
  - var title = 'Dashboard'

block body
  .container.dashboard
    .row.api-keys
      ul.list-group
        .col-xs-offset-1.col-xs-10
          li.list-group-item.api-key-container
            .left
              strong API Key ID:
              span.api-key-id #{user.apiKeys.items[0].id}
            .right
              strong API Key Secret:
              span.api-key-secret #{user.apiKeys.items[0].secret}
    .row.widgets
      .col-md-offset-1.col-md-5
        .panel.panel-primary
          .panel-heading.text-center
            h3.panel-title Analytics
          .analytics-content.text-center
            span.total-queries #{user.customData.totalQueries}
            br
            span
              i.
                *total queries
      .col-md-5
        .panel.panel-primary
          .panel-heading.text-center
            h3.panel-title Billing
          .billing-content.text-center
            span.account-balance $#{(user.customData.balance / 100).toFixed(2)}
            br
            span
              i.
                *current account balance
            form(action='/dashboard/charge', method='POST')
              script.stripe-button(
                src = 'https://checkout.stripe.com/checkout.js',
                data-email = '#{user.email}',
                data-key = '#{stripePublishableKey}',
                data-name = '#{siteTitle}',
                data-amount = '2000',
                data-allow-remember-me = 'false'
              )

Static Assets

Now that we’ve taken a quick look at the views, let’s take a quick look at the static assets we’ll be using.

In our case, since there’s not a lot of styling done here — we’ve only got a single CSS file:

/*
 * Navbar settings.
 */
ul.nav.navbar-nav {
  float: right;
}

li.create-account > a {
  color: #fff !important;
}

/*
 * Index page settings.
 */
.index h1 {
  margin-top: 2em;
}

.index .jumbotron {
  margin-top: 4em;
}

.index button {
  margin-top: 4em;
  font-size: 1em;
}

/*
 * Dashboard page settings.
 */
.dashboard .api-keys {
  margin-top: 3em;
}

.dashboard .api-key-container {
  min-height: 4em;
}

.dashboard .widgets {
  margin-top: 4em;
}

.dashboard .api-key-secret {
  color: red;
}

.dashboard h3 {
  font-size: 1.2em !important;
}

.dashboard span.api-key-id, .dashboard span.api-key-secret {
  font-family: "Lucida Console", Monaco, monospace;
  margin-left: .5em;
}

.dashboard .left {
  float: left;
}

.dashboard .right {
  float: right;
}

.dashboard .panel {
  padding-bottom: 2em;
}

.dashboard .panel-heading {
  margin-bottom: 2em;
}

.dashboard .analytics-content, .dashboard .billing-content {
  padding-left: 2em;
  padding-right: 2em;
}

.dashboard .account-balance, .dashboard .total-queries {
  font-size: 2em;
}

.dashboard form {
  margin-top: 2em;
}

/*
 * Pricing page settings.
 */
.pricing .price-box {
  border: 2px solid #f8f5f0;
  border-radius: 6px;
  margin-top: 4em;
  margin-bottom: 4em;
}

.pricing h2 {
  margin-bottom: 1em;
}

.pricing .end {
  margin-bottom: 2em;
}

/*
 * Documentation page settings.
 */
.docs h1 {
  margin-bottom: 2em;
}

.docs h2 {
  margin-top: 2em;
  margin-bottom: 2em;
}

.docs h3 {
  /*padding-left: 2em;*/
  font-weight: bold;
}

.docs span {
  font-size: 1.2em;
  padding-left: 2.7em;
  font-weight: bold;
  margin-top: 1em;
  margin-bottom: .5em;
  display: block;
}

.docs .description {
  font-size: 1.2em;
  padding-left: 2.6em;
}

.docs .table-box {
  padding-left: 3em !important;
  margin-top: 1em !important;
}

.docs pre {
  margin-left: 3em;
}

package.json and bower.json

Now, let’s get into some real code!

Below is the package.json file that declares all of our Node.js dependencies, and makes installing this application simple:

{
  "name": "api-service-starter",
  "version": "0.0.0",
  "description": "An API service starter kit for Node.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "api",
    "service",
    "starter",
    "kit"
  ],
  "author": "Randall Degges",
  "license": "UNLICENSE",
  "dependencies": {
    "async": "^0.9.0",
    "body-parser": "^1.12.3",
    "express": "^4.12.3",
    "express-stormpath": "^1.0.4",
    "jade": "^1.9.2",
    "request": "^2.55.0",
    "stripe": "^3.3.4",
    "twilio": "^2.0.0"
  }
}

To install this project, you can simply run $ npm install from the command line — this will automatically download all Node dependencies for ya =)

Likewise, you can also use bower to automatically download and install all front-end dependencies via $ bower install. The bower.json file makes this possible:

{
  "name": "api-service-starter",
  "main": "index.js",
  "version": "0.0.0",
  "authors": [
    "Randall Degges <r@rdegges.com>"
  ],
  "description": "An API service starter kit for Node.",
  "keywords": [
    "api",
    "service",
    "starter",
    "kit"
  ],
  "license": "UNLICENSE",
  "private": true,
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "jquery": "~2.1.3",
    "bootstrap": "~3.3.4",
    "respond": "~1.4.2",
    "html5shiv": "~3.7.2",
    "bootswatch": "~3.3.4+1"
  }
}

Application Setup

Now that we’ve covered the basics, let’s take a look at what makes our project tick: the index.js file. This holds the main Express.js web application, configures our libraries, and initializes our web server:

'use strict';

var async = require('async');
var express = require('express');
var stormpath = require('express-stormpath');

var apiRoutes = require('./routes/api');
var privateRoutes = require('./routes/private');
var publicRoutes = require('./routes/public');

// Globals
var app = express();

// Application settings
app.set('view engine', 'jade');
app.set('views', './views');

app.locals.costPerQuery = parseInt(process.env.COST_PER_QUERY);
app.locals.siteTitle = 'BTC SMS';
app.locals.stripePublishableKey = process.env.STRIPE_PUBLISHABLE_KEY;

// Middlewares
app.use('/static', express.static('./static', {
  index: false,
  redirect: false
}));
app.use('/static', express.static('./bower_components', {
  index: false,
  redirect: false
}));
app.use(stormpath.init(app, {
  enableAccountVerification: true,
  expandApiKeys: true,
  expandCustomData: true,
  redirectUrl: '/dashboard',
  secretKey: 'blah',
  postRegistrationHandler: function(account, req, res, next) {
    async.parallel([
      // Set the user's default settings.
      function(cb) {
        account.customData.balance = 0;
        account.customData.totalQueries = 0;
        account.customData.save(function(err) {
          if (err) return cb(err);
          cb();
        });
      },
      // Create an API key for this user.
      function(cb) {
        account.createApiKey(function(err, key) {
          if (err) return cb(err);
          cb();
        });
      }
    ], function(err) {
      if (err) return next(err);
      next();
    });
  }
}));

// Routes
app.use('/', publicRoutes);
app.use('/api', stormpath.apiAuthenticationRequired, apiRoutes);
app.use('/dashboard', stormpath.loginRequired, privateRoutes);

// Server
app.listen(process.env.PORT || 3000);

The first thing we’ll do is import all of the libraries necessary, as well as our route code (which we’ll hook up in a bit).

The next thing we’ll do is define our Express.js application, and tell is that we’re going to be using the Jade template language for our view code:

var app = express();

// Application settings
app.set('view engine', 'jade');
app.set('views', './views');

Once that’s been done, we’ll initialize some global settings:

app.locals.costPerQuery = parseInt(process.env.COST_PER_QUERY);
app.locals.siteTitle = 'BTC SMS';
app.locals.stripePublishableKey = process.env.STRIPE_PUBLISHABLE_KEY;

The COST_PER_QUERY and STRIPE_PUBLISHABLE_KEY values are being pulled out of environment variables. Instead of hard-coding your credentials into your source code, storing them in environmental variables is typically a better thing to do as you don’t need to worry about accidentally exposing your credentials.

The COST_PER_QUERY environment variable tells our app how many cents we should charge for each successful API request — in our case, we’ll set this to 2.

The STRIPE_PUBLISHABLE_KEY environment variable should be set to our Stripe Publishable Key that we retrieved earlier on when we created a Stripe account.

Here’s an example of how you might set these variables from the command line:

$ export COST_PER_QUERY=2
$ export STRIPE_PUBLISHABLE_KEY=xxx

Next, we’ll use the express.static built-in middleware to properly serve our app’s static assets:

app.use('/static', express.static('./static', {
  index: false,
  redirect: false
}));
app.use('/static', express.static('./bower_components', {
  index: false,
  redirect: false
}));

And… After that, we’ll initialize the Stormpath library:

app.use(stormpath.init(app, {
  enableAccountVerification: true,
  expandApiKeys: true,
  expandCustomData: true,
  redirectUrl: '/dashboard',
  secretKey: 'blah',
  postRegistrationHandler: function(account, req, res, next) {
    async.parallel([
      // Set the user's default settings.
      function(cb) {
        account.customData.balance = 0;
        account.customData.totalQueries = 0;
        account.customData.save(function(err) {
          if (err) return cb(err);
          cb();
        });
      },
      // Create an API key for this user.
      function(cb) {
        account.createApiKey(function(err, key) {
          if (err) return cb(err);
          cb();
        });
      }
    ], function(err) {
      if (err) return next(err);
      next();
    });
  }
}));

The express-stormpath library makes securing our website really easy.

I’ll cover the different options, and what they do below.

  • The enableAccountVerification flag tells the library that when a user signs up for our site, we should email them a link to click on to verify their email address.
  • Stormpath let’s you provision API keys for each user account. Since we’re building an API service, this is ideal. The expandApiKeys option makes Stormpath automatically pull down a user’s API key information and keep it cached to speed up API requests.
  • Stormpath also lets you store user data using something called custom data. Basically, each user can have a big old JSON dictionary stored on their account with whatever data you want. In our case, we’ll be using this to store a user’s account balance as well as the total amount of requests they’ve made to our service. The expandCustomData flag makes this operation faster by automatically downloading this information and keeping it in a local cache.
  • The redirectUrl setting tells Stormpath where to redirect a user after they’ve logged into the site.
  • The secretKey setting should be a long random string that stays private — this is used to secure the user sessions.
  • Lastly, the postRegistrationHandler lets us run code after a new user has signed up on the site. What we’ll do here is initialize a user by creating an API key for them automatically, as well as setting their account balance and total queries to 0.

Now that we’ve configured all our middleware, the last thing we need to do is include our route code:

app.use('/', publicRoutes);
app.use('/api', stormpath.apiAuthenticationRequired, apiRoutes);
app.use('/dashboard', stormpath.loginRequired, privateRoutes);

The way this works is like so:

  • First, we’ll tell Express to serve our public routes.
  • Then, we’ll tell Express to serve our API routes, but to require that a user authenticate with their API key before being allowed to access any of these routes.
  • Lastly, we’ll tell Express to render our private (dashboard) routes, but to require a user to be logged in via the website to access these pages.

The Stormapth middlewares included here automatically handle all of the authentication logic for us 100%. If we try to access the /dashboard page without being logged into the website, for instance, we’ll be immediately redirected to the login page and forced to authenticate.

If we try to access an API route without using Basic Auth, we’ll get a 401 UNAUTHORIZED message with a nice JSON error.

The Routes

The main part of our application is the routes. This is where all the magic happens: billing, API code, SMS code, etc.

Let’s take a look at each route, and dissect how exactly they work.

public.js

First, let’s look at our public routes. These routes are responsible for serving our ‘public’ pages on the website:

'use strict';

var express = require('express');

// Globals
var router = express.Router();

// Routes
router.get('/', function(req, res) {
  res.render('index');
});

router.get('/pricing', function(req, res) {
  res.render('pricing');
});

router.get('/docs', function(req, res) {
  res.render('docs');
});

// Exports
module.exports = router;

As you can see, nothing is happening here except that we’re rendering our pre-defined Jade templates.

private.js

The private route file contains only a single route: our dashboard page. Because our BTC SMS app only has a single page for logged-in users (the dashboard) — this is where that logic is contained.

If we were building a larger site, which had many private pages that only logged in users could access, they’d be included here also:

'use strict';

var bodyParser = require('body-parser');
var express = require('express');
var stormpath = require('express-stormpath');
var stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

// Globals
var router = express.Router();

// Middlewares
router.use(bodyParser.urlencoded({ extended: true }));

// Routes
router.get('/', function(req, res) {
  res.render('dashboard');
});

router.post('/charge', function(req, res, next) {
  stripe.charges.create({
    amount: 2000,
    currency: 'usd',
    source: req.body.stripeToken,
    description: 'One time deposit for ' + req.user.email + '.'
  }, function(err, charge) {
    if (err) return next(err);
    req.user.customData.balance += charge.amount;
    req.user.customData.save(function(err) {
      if (err) return next(err);
      res.redirect('/dashboard');
    });
  });
});

// Exports
module.exports = router;

Let’s see how this works.

First, after creating an Express router, we’re using the bodyParser middleware to decode form data.

On this page, we’ll have a form that allows us to accept payment from a user, and because of this, we’ll need the ability to read the form data we’re receiving. This is what the bodyParser middleware is used for:

router.use(bodyParser.urlencoded({ extended: true  }));

This middleware let’s us access form data via req.body. So, for instance, if a form field called username was posted to us, we could access that data by saying req.body.username.

Next, we’ll register a router handler for the GET requests to our dashboard page:

router.get('/', function(req, res) {
  res.render('dashboard');
});

This code simply renders the dashboard page if a user visits the /dashboard URL in their browser.

Next, we’ll register a POST handler for the dashboard page:

router.post('/charge', function(req, res, next) {
  // stuff
});

This code will get run if a user attempts to deposit money into their account:

Stripe Deposit Money

What happens in our template code is all of the card collection and verification stuff. When we receive this POST request from the browser, what that means is that the user’s card is valid, and Stripe has given us permission to actually charge this user some money.

In our case, we’ll be charging users a flat fee of 20$.

Using the stripe library, we’ll then charge the user’s card:

stripe.charges.create({
  amount: 2000,
  currency: 'usd',
  source: req.body.stripeToken,
  description: 'One time deposit for ' + req.user.email + '.'
}, function(err, charge) {
  if (err) return next(err);
  req.user.customData.balance += charge.amount;
  req.user.customData.save(function(err) {
    if (err) return next(err);
    res.redirect('/dashboard');
  });
});

Once the user’s card has been successfully charged, we’ll also update the user account’s balance, so that we now know how much money this user has paid us.

And… That’s it for billing! Quite easy, right?

api.js

The last route we need to cover is the API route. Since our API service only has a single API call, this file only holds one API route. If we were building a more complex API service, however, this file might be a lot longer:

'use strict';

var bodyParser = require('body-parser');
var express = require('express');
var request = require('request');
var twilio = require('twilio')(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

// Globals
var router = express.Router();
var BTC_EXCHANGE_RATE;
var COST_PER_QUERY = parseInt(process.env.COST_PER_QUERY);

// Middlewares
router.use(bodyParser.json());

// Routes
router.post('/message', function(req, res) {
  if (!req.body || !req.body.phoneNumber) {
    return res.status(400).json({ error: 'phoneNumber is required.' });
  } else if (!BTC_EXCHANGE_RATE) {
    return res.status(500).json({ error: "We're having trouble getting the exchange rates right now. Try again soon!" });
  } else if (req.user.customData.balance < COST_PER_QUERY) {
    return res.status(402).json({ error: 'Payment required. You need to deposit funds into your account.' });
  }

  var message = '1 Bitcoin is currently worth $' + BTC_EXCHANGE_RATE  + ' USD.';

  twilio.sendMessage({
    to: req.body.phoneNumber,
    from: process.env.TWILIO_PHONE_NUMBER,
    body: message
  }, function(err, resp) {
    if (err) return res.status(500).json({ error: "We couldn't send the SMS message. Try again soon!" });

    req.user.customData.balance -= COST_PER_QUERY;
    req.user.customData.totalQueries += 1;
    req.user.customData.save();

    res.json({ phoneNumber: req.body.phoneNumber, message: message, cost: COST_PER_QUERY });
  });
});

// Functions
function getExchangeRates() {
  request('http://api.bitcoincharts.com/v1/weighted_prices.json', function(err, resp, body) {
    if (err || resp.statusCode !== 200) {
      console.log('Failed to retrieve BTC exchange rates.');
      return;
    }

    try {
      var data = JSON.parse(body);
      BTC_EXCHANGE_RATE = data.USD['24h'];
      console.log('Updated BTC exchange rate: ' + BTC_EXCHANGE_RATE + '.');
    } catch (err) {
      console.log('Failed to parse BTC exchange rates.');
      return;
    }
  });
}

// Tasks
getExchangeRates();
setInterval(getExchangeRates, 60000);

// Exports
module.exports = router;

Like our private.js routes, we’ll also be using the bodyParser middleware here to read in API request data.

We’ll also be making use of the twilio library to send SMS messages to users, as well as the request library to fetch the current Bitcoin exchange rates from bitcoincharts.

The bitcoincharts site provides a publicly available API that lets you grab the current Bitcoin exchange rates. This is where we’ll be grabbing our Bitcoin value information from =) You can find more information on this here: http://api.bitcoincharts.com/v1/weighted_prices.json

So, once we’ve defined our Express router, the first thing we’ll do is declare some globals:

var BTC_EXCHANGE_RATE;
var COST_PER_QUERY = parseInt(process.env.COST_PER_QUERY);

The BTC_EXCHANGE_RATE variable will be set to the current value of Bitcoin in USD, and updated frequently. This is what we’ll use when we send out SMS messages to users.

The COST_PER_QUERY variable is the amount of money (in cents) that we’ll charge a user for each successful API request made.

Next, we’ll define a helper function called getExchangeRates which queries the bitcoin charts API service to find the current value of a Bitcoin:

function getExchangeRates() {
  request('http://api.bitcoincharts.com/v1/weighted_prices.json', function(err, resp, body) {
    if (err || resp.statusCode !== 200) {
      console.log('Failed to retrieve BTC exchange rates.');
      return;
    }

    try {
      var data = JSON.parse(body);
      BTC_EXCHANGE_RATE = data.USD['24h'];
      console.log('Updated BTC exchange rate: ' + BTC_EXCHANGE_RATE + '.');
    } catch (err) {
      console.log('Failed to parse BTC exchange rates.');
      return;
    }
  });
}

This function simply makes the request, then extracts the data. Finally it assigns the current value to the global BTC_EXCHANGE_RATE variable defined earlier.

After that’s done, we’ll invoke this function in two ways:

getExchangeRates();
setInterval(getExchangeRates, 60000);

First, we’ll call it immediately so that as soon as our program starts, we get the current BTC value.

Next, we’ll call it on a setInterval job, which executes once per hour (in milliseconds). This ensures that every hour we’ll update the BTC exchange rate to the latest values.

Finally, we’ll implement our API route /api/message, which is what developers will be using to send SMS messages with the current BTC exchange rate information:

router.post('/message', function(req, res) {
  if (!req.body || !req.body.phoneNumber) {
    return res.status(400).json({ error: 'phoneNumber is required.' });
  } else if (!BTC_EXCHANGE_RATE) {
    return res.status(500).json({ error: "We're having trouble getting the exchange rates right now. Try again soon!" });
  } else if (req.user.customData.balance < COST_PER_QUERY) {
    return res.status(402).json({ error: 'Payment required. You need to deposit funds into your account.' });
  }

  var message = '1 Bitcoin is currently worth $' + BTC_EXCHANGE_RATE  + ' USD.';

  twilio.sendMessage({
    to: req.body.phoneNumber,
    from: process.env.TWILIO_PHONE_NUMBER,
    body: message
  }, function(err, resp) {
    if (err) return res.status(500).json({ error: "We couldn't send the SMS message. Try again soon!" });

    req.user.customData.balance -= COST_PER_QUERY;
    req.user.customData.totalQueries += 1;
    req.user.customData.save();

    res.json({ phoneNumber: req.body.phoneNumber, message: message, cost: COST_PER_QUERY });
  });
});

This API route will:

  • Check to ensure that the incoming request data is formatted properly. Each incoming request must supply a phone number so we know who to SMS.
  • If no phone number is supplied, we’ll return an HTTP 400 response, along with an appropriate error.
  • If the BTC exchange rate isn’t available for some reason (maybe the bitcoin charts API is down), we’ll return a 500 status code along with an appropriate JSON error message.
  • Lastly, if the user doesn’t have enough money in their account to pay for this request, we’ll reject the request with a 402 status code an another appropriate error message.

Once we’ve done the error handling stuff, we’ll use the Twilio library to send an SMS message from our pre-purchased phone number (process.env.TWILIO_PHONE_NUMBER), with our pre-formatted message.

If, for any reason, the SMS message sending fails, we’ll return a 500 with an error message.

If the SMS message succeeds, we’ll subtract 2 cents from the user’s account balance, increment the user’s total queries counter, and then return a successful JSON response message.

It’s that simple!

Running the App

To run the app, as you saw through the code explanations, you’ll need to define some environment variables.

Here is a full list of the required environment variables you need to set to run this thing:

$ export COST_PER_QUERY=2
$ export STORMPATH_API_KEY_ID=xxx
$ export STORMPATH_API_KEY_SECRET=xxx
$ export STORMPATH_APPLICATION=https://api.stormpath.com/v1/applications/xxx
$ export STRIPE_SECRET_KEY=xxx
$ export STRIPE_PUBLISHABLE_KEY=xxx
$ export TWILIO_ACCOUNT_SID=xxx
$ export TWILIO_AUTH_TOKEN=xxx
$ export TWILIO_PHONE_NUMBER=+18882223333

These variables will be used automatically in the project code to make things work as needed.

Once these variables have been defined, you can then run your own instance of the BTC SMS app by saying:

$ node index.js

And then visiting http://localhost:3000.

To deposit money into your account using Stripe (in test mode), you can use the credit card number 424242424242, with any fake expiration date and CVC number. This will deposit funds into your account.

Lastly, to make successful API requests, you can use the cURL command line tool like so:

$ curl -v —user ‘API_KEY_ID:API_KEY_SECRET’ -H ‘Content-Type: application/json’ —data ‘{“phoneNumber”: “+18882223333”}’ ‘http://127.0.0.1:3000/api/message

Be sure to substitute in your own phone number and API credentials (taken from the BTC SMS dashboard page).

What Did We Learn?

Building a simple API service isn’t really all that hard. In just a few hours you can structure, plan, and implement a full-fledged API company with only a few small, free-to-use services.

The old days where launching a company took a lot of time and effort are long gone. Using API services to speed up your development can save you a bunch of time, effort, and problems.

I hope that this tutorial gave you a little bit of inspiration, taught you something new, and hopefully gave you some new ideas for your own cool projects.

Be sure to check out Stormpath, Twilio, and Stripe for your next projects =)

Oh — and if you have any questions, leave us a comment below!

PS: If you’re currently learning how to build API services and do stuff with Node.js, I’d recommend really writing this code out and playing around with it yourself. There’s no better way to learn this stuff than by messing around with it on your own =)

-Randall


Viewing all articles
Browse latest Browse all 278