Update 2/29/16: These code examples have been updated to reflect the 3.0 release of the express-stormpath integration.

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

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" } 1 2 3 4 5 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:

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:

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.

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:

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:

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:

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:

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

=)

Lastly, you’ll want to navigate to your Stormpath Directories Page, click on your Directory, then go to the Workflows tab and explicitly enable the Verification Workflow.

Here’s how it works:

In the Verification Email tab, enable the workflow. This means we’ll send email to all newly registered users, making them confirm their accounts.

Modify the Link Base URL setting, and set it to http://localhost:3000/verified . This is where users will be redirected once they’ve clicked the link in their email to verify their account.

That’s it! Stormpath will handle the rest =)

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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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.

: 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.

: 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.

: 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.

: 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).

: 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.

: This file contains our API code. routes/private.js : This file contains the code that renders our private pages (the customer dashboard).

: This file contains the code that renders our private pages (the customer dashboard). routes/public.js : This file contains our public pages code.

: This file contains our public pages code. static : This folder holds all of our static assets: CSS, Javascript, etc.

: 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.

: This is our CSS file to make things look decent. views : This folder holds our template code.

: 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.

: 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.

: This file holds our private dashboard page code. This includes some billing logic. views/docs.jade : This file holds our public API documentation.

: This file holds our public API documentation. views/index.jade : This is our main home page.

: 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.

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') 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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.

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! 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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.

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' 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 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: [email protected] > 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'

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}¢ / 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! 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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}¢ / 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 !

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' ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 /* * 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": "^3.0.0", "jade": "^1.9.2", "request": "^2.55.0", "stripe": "^3.3.4", "twilio": "^2.0.0" } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 { "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" : "^3.0.0" , "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" } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 { "name" : "api-service-starter" , "main" : "index.js" , "version" : "0.0.0" , "authors" : [ "Randall Degges < [email protected] >" ] , "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, { expand: { apiKeys: true, customData: true }, web: { login: { nextUri: '/dashboard' } }, 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); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 '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 , { expand : { apiKeys : true , customData : true } , web : { login : { nextUri : '/dashboard' } } , 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'); 1 2 3 4 5 6 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; 1 2 3 4 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 1 2 3 $ 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 })); 1 2 3 4 5 6 7 8 9 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, { expand: { apiKeys: true, customData: true }, web: { login: { nextUri: '/dashboard' } }, 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(); }); } })); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 app . use ( stormpath . init ( app , { expand : { apiKeys : true , customData : true } , web : { login : { nextUri : '/dashboard' } } , 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.

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.

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.

flag makes this operation faster by automatically downloading this information and keeping it in a local cache. The nextUri setting tells Stormpath where to redirect a user after they’ve logged into the site.

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.

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); 1 2 3 4 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.

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; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 '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.

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; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 '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 })); 1 2 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'); }); 1 2 3 4 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 }); 1 2 3 4 router . post ( '/charge' , function ( req , res , next ) { // stuff } ) ;

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

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'); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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?

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; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 '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); 1 2 3 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; } }); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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); 1 2 3 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 }); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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 1 2 3 4 5 6 7 8 9 10 $ 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 1 2 $ 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 4242424242424242 , 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' 1 2 $ 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