Today we’ll learn how to build a RESTful API with Express and how to test the API manually with Postman. Don't worry: testing the API with Postman is a necessary skill, but we'll also learn how to do automated testing later.
- Build an API with Express
- Manually test the API with Postman
Here's what the API will do:
- Handle CRUD for a resource (this lesson uses swords, but you should choose something else.)
- Have a standard compliant URL structure (
/api/swords
and/api/swords/:sword_id
) - Use semantic HTTP verbs (GET, POST, PUT, and DELETE)
- Use semantic status codes to keep clients informed
- Return JSON data to all requests
In the past, we've consumed
APIs as the client, but it's likely that the APIs you've consumed didn't support full CRUD actions. So, in some ways there are parallels (the API provides data to the client when it gets a request), but this API is going to support much more than simple get requests.
Knowing how to build and consume your own application APIs is 100% necessary to provide data to full-stack, single page apps with thick clients like Angular, Ember, React, etc.
Close down any other projects you're working on. Get out your favorite note taking mechanism, and take a deep breath. Quickly review all of the headers in this document, then start reading from the top.
Let’s look at all the files we will need to create our API. We will need to define our Node packages, start our server using Express, define our model, declare our routes using Express, and last but not least, test our API.
Here is our file structure. We won’t need very much, because we're building the simplest possible API server.
* app/
* node_modules/
* package.json
* app.js
* routes/
* swords.js
We're not going to use the express generator to create our API because it comes with lots of view related modules. Instead, we're going to start this like any other node app: with npm init
.
Run npm init
inside the root of a newly created application folder.
We're going to need express
, knex
, pg
, and body-parser
to get going, so install those with npm. Note: Be sure to use the --save flag so they are added to package.json.
After installing these, your package.json should look something like this:
####package.json
{
"name": "express-api",
"main": "app.js",
"dependencies": {
"body-parser": "^1.15.2",
"express": "^4.14.0",
"knex": "^0.12.6",
"pg": "^6.1.2"
}
}
Crack open the app.js file to setup our app.
####app.js
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
var swords = require('./routes/swords')
app.use('/api/swords', swords);
app.listen(process.env.PORT || 8080);
console.log('Woot, server started');
All of the above code should look familiar from the express generator you used before. It's the absolute minimum needed to get an express app going. If you haven't already, create a routes
directory for your application routes.
You're going to set up routes like you do any others, with the exception of two things.
- The response will not use the
render
method; instead, it's going to use thejson
method. Take a few moments to read the Express docs on this method. - You will include explicit, semantic HTTP status codes.
####/routes/swords.js
var express = require('express');
var router = express.Router();
router.get('/', function(req, res) {
res.status(200).json({ message: 'rawr! you did it!' });
});
module.exports = router
Let’s make sure that everything is working up to this point. We will start our Node app and then send a request to the one route we defined to make sure we get a response.
Let’s start the server. From the command line, type:
$ nodemon app.js
Note: Why doesn't npm run start
work here? What would you do to make it work?
You should see your app start up in the terminal.
Now that we know our application is up and running, let’s test it.
Postman will help us test our API. It will basically send HTTP requests to a URL of our choosing. We can even pass in parameters (which we will soon) and authentication (which we won’t need for this tutorial).
Open up Postman to begin.
All you have to do is enter your request URL, select an HTTP verb, and click Send. Simple enough right?
Here’s the moment we’ve been waiting for. Does our application work the way we configured it? Enter http://localhost:8080/api/swords into the URL. GET is what we want since we just want to get data. Now click Send.
Win. We've just confirmed everything is working as expected. Let’s wire up our database so we can start performing CRUD operations on some swords.
To make a database, we'll follow the following steps:
- Create a SQL database
- Create a
swords
table - Create a
knexfile
- Create a module that connects to our database by reading our
knexfile
Run createdb swords
in your terminal to create your swords database, or login to psql
and run CREATE DATABASE swords;
. These are equivalent syntax, so don't do both.
Create a table by logging into your new postgres database and running CREATE TABLE swords...
or by using knex migrations.
####Schema
CREATE TABLE swords (
id serial PRIMARY KEY,
type text,
is_magical boolean,
attack int,
sp_attack int
);
Run knex init
to create yourself a knexfile
, then update your knexfile
to read from your new postgres database.
####knexfile.js
development: {
client: 'postgres',
connection: "postgres://localhost:5432/swords"
},
Make yourself a knex.js
file, and put it in your db
folder. Then, add this code:
####/db/knex.js
var environment = process.env.NODE_ENV || 'development';
var config = require('../knexfile.js')[environment];
module.exports = require('knex')(config);
This helps us avoid repeating all of that configuration over and over again, each time we want to connect to our database.
Just include the configuration module at the top of your swords route. ####/routes/swords.js
var express = require('express');
var router = express.Router();
var knex = require('../db/knex');
...
module.exports = router
We will use an instance of the Express Router to handle all of our routes. Here is an overview of the routes we will require, what they will do, and the HTTP Verb used to access it.
Route | HTTP Verb | Description |
---|---|---|
/api/swords | GET | Get all swords. |
/api/swords | POST | Create a sword. |
/api/swords/:sword_id | GET | Get a single sword. |
/api/swords/:sword_id | PUT | Update a sword with new info. |
/api/swords/:sword_id | DELETE | Delete a sword. |
This will cover the basic routes needed for an API. This also keeps to a good format where we have kept the actions we need to execute (GET, POST, PUT, and DELETE) as HTTP verbs.
Before you proceed, you should take the time to read through Runscope's HTTP Statuses explaination (and check out http.cat). It will provide context for the remainder of this lesson. So, take note of where you are in the development process on this app, then click the above link to start learning about status codes.
Getting a sword and creating a sword will both be handled using the /api/swords
route, but each with different HTTP methods POST and GET respectively.
We will add the new route to handle POST and then test it using Postman.
####routes/swords.js
router.post('/', function(req, res) {
knex('swords').insert(req.body).returning('*').then(function(sword) {
res.status(201).json(sword);
});
})
To test this out in Postman you will need to:
- Change GET to POST
- Select the Body section to add form data
- Select x-www-form-urlencoded (the same type of data HTML forms send)
- Enter the key value pairs you want to send to the API
- Then click Send
We get back a successful message (notice the 201 created status code) that our sword has been created. Let’s handle the API route to get all the swords so that we can see the sword we just created.
This will be a simple route that just returns all of the swords from the database. Reference the above table to determine what route and HTTP verb should make that happen... Take your time. I'll wait. :) Once you decide on the route, proceed to the next section.
You should have decided on a GET require for the index route. We already defined a route GET at the swords index, so lets change that up a bit.
router.get('/', function(req, res) {
knex('swords').select('*').then(function (swords) {
res.status(200).json(swords);
})
});
Testing this one is simple. Just send a GET require to the /api/swords
route.
Now that the index route is covered, it's time to work with routes that use the resource ID.
These routes will:
- Get a single resource
- Update a single resource's info
- Delete a resource
Reference the chart above to figure out what route should be responsible for this, then consider the following code:
router.get('/:id', function(req, res) {
console.log(req.params.id);
})
Read the above code for a few minutes and try to answer these questions. You should be able to come up with an answer within 10 minutes by reading the code, and trying things in your application code.
- How do you send a GET request to invoke this route handler?
- What information does the params tell us about the request resource?
- How then would we locate that resource in the database?
- Once located, what should be done with the data?
Pause and Think
- A1: Send a GET request to
localhost:8080/api/swords/someidnumber
- A2: It should provide the id of the resource as made available in the URL. It can be accessed with
req.params.id
- A3: Once you have the ID, it's just a matter of searching for that resource with knex.
- A4: Well, it's a GET request for a sword with a specific ID, so you should send back that sword. That's what the client wants! :)
The solution code looks something like this:
router.get('/:id', function(req, res) {
knex('swords').select('*').where({id: req.params.id}).then(function (sword) {
res.status(200).json(sword)
})
})
You should send a GET request to this route (see answer A1) to confirm it's working before moving on. Just note that you will need to replace someidnumber
in the URL with an actual document ID from the database.
The update route is also going to be defined for the above route, but using the appropriate HTTP method. If router.get
handles GET requests and router.post
handles POST requests, can you guess what router method handles PUT requests? Yup. It's that simple.
router.put('/:id', function(req, res) {
console.log(req.params.id);
})
Take a few minutes to lookup the findAndModify docs in MongoDB. It's not linked here so you can practice finding the correct docs quickly. Go ahead. Google it! :)
Given the many ways to use findAndModify, you may consider something like this:
router.put('/:id', function(req, res) {
knex('swords').update(req.body).where({id: req.params.id}).returning('*').then(function (sword) {
res.json(sword)
})
})
You can test this route by sending a PUT request to api/swords/someidnumber
. Use x-www-form-urlencoded
to specify which document fields you want to update.
To delete a resource from the API, a DELETE request needs to be sent to the correct resource (api/swords/someidnumber
)
You should be able to guess by now how to accept delete requests in the router. Instead of writing it out, I'm going to leave it as an exercise for you.
- How do you accept different HTTP methods (like DELETE) in your routes?
- How do you delete record from a SQL database?
- How do you know which record to delete?
- What status code is associated with a response for DELETE?
To complete this exercise, you should:
- Complete the routes for all CRUD actions for the swords resource
- Create new routes to manage CRUD actions for a
potions
resource.