In this article, I’ll be diving into sending and querying data with the API we built in http://brianmorrison.me/blog/hello-world-nodejs-api. I’ll start by adding a new route to request data and explaining the concept of routing in the API. Then we’ll move into using query parameters to filter the data being requested from the API. Finally, I’ll explain how to use a POST along with a package called body-parser to send data to the API for processing.

API Request Routing

In APIs, Routing refers to handling requests in different ways based on the URL path. In the last article, we sent a request to http://localhost:3000 with no path, which means the route is technically /. But you can expand on your API by adding different routes. For example;

  • http://localhost:3000/books can handle various requests as it relates to the books entity
  • http://localhost:3000/movies would handle requests related to the movies entity

A common best practice is to name the route a pluralized version of the specific entity or concept the route relates to.

Let’s move on with our books concept and add a route for books. I’m going to start by adding some dummy data into our index.js file for books. Add the following after the first two lines in the file.

let books = [{
    "id": 1,
    "isbn": "9781593275846",
    "title": "Eloquent JavaScript, Second Edition",
    "subtitle": "A Modern Introduction to Programming",
    "author": "Marijn Haverbeke",
    "published": "2014-12-14T00:00:00.000Z",
    "publisher": "No Starch Press",
    "pages": 472,
    "description": "JavaScript lies at the heart of almost every modern web application, from social apps to the newest browser-based games. Though simple for beginners to pick up and play with, JavaScript is a flexible, complex language that you can use to build full-scale applications.",
    "website": "http://eloquentjavascript.net/"
  },
  {
    "id": 2,
    "isbn": "9781449331818",
    "title": "Learning JavaScript Design Patterns",
    "subtitle": "A JavaScript and jQuery Developer's Guide",
    "author": "Addy Osmani",
    "published": "2012-07-01T00:00:00.000Z",
    "publisher": "O'Reilly Media",
    "pages": 254,
    "description": "With Learning JavaScript Design Patterns, you'll learn how to write beautiful, structured, and maintainable JavaScript by applying classical and modern design patterns to the language. If you want to keep your code efficient, more manageable, and up-to-date with the latest best practices, this book is for you.",
    "website": "http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/"
  },
  {
    "id": 3,
    "isbn": "9781449365035",
    "title": "Speaking JavaScript",
    "subtitle": "An In-Depth Guide for Programmers",
    "author": "Axel Rauschmayer",
    "published": "2014-02-01T00:00:00.000Z",
    "publisher": "O'Reilly Media",
    "pages": 460,
    "description": "Like it or not, JavaScript is everywhere these days-from browser to server to mobile-and now you, too, need to learn the language or dive deeper than you have. This concise book guides you into and through JavaScript, written by a veteran programmer who once found himself in the same position.",
    "website": "http://speakingjs.com/"
  }
]

Then add the following under app.get. Note that we’re going to use res.json(books) to tell express we want to return JSON instead of plain text.

app.get('/books', async (req, res) => {
  res.json(books);
})

Your index.js file should look like this now.

// Import express and create a new express app
const express = require('express');
const app = express();

// Define some dummy book data, courtesy of https://gist.github.com/nanotaboada/6396437
let books = [{
    "id": 1,
    "isbn": "9781593275846",
    "title": "Eloquent JavaScript, Second Edition",
    "subtitle": "A Modern Introduction to Programming",
    "author": "Marijn Haverbeke",
    "published": "2014-12-14T00:00:00.000Z",
    "publisher": "No Starch Press",
    "pages": 472,
    "description": "JavaScript lies at the heart of almost every modern web application, from social apps to the newest browser-based games. Though simple for beginners to pick up and play with, JavaScript is a flexible, complex language that you can use to build full-scale applications.",
    "website": "http://eloquentjavascript.net/"
  },
  {
    "id": 2,
    "isbn": "9781449331818",
    "title": "Learning JavaScript Design Patterns",
    "subtitle": "A JavaScript and jQuery Developer's Guide",
    "author": "Addy Osmani",
    "published": "2012-07-01T00:00:00.000Z",
    "publisher": "O'Reilly Media",
    "pages": 254,
    "description": "With Learning JavaScript Design Patterns, you'll learn how to write beautiful, structured, and maintainable JavaScript by applying classical and modern design patterns to the language. If you want to keep your code efficient, more manageable, and up-to-date with the latest best practices, this book is for you.",
    "website": "http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/"
  },
  {
    "id": 3,
    "isbn": "9781449365035",
    "title": "Speaking JavaScript",
    "subtitle": "An In-Depth Guide for Programmers",
    "author": "Axel Rauschmayer",
    "published": "2014-02-01T00:00:00.000Z",
    "publisher": "O'Reilly Media",
    "pages": 460,
    "description": "Like it or not, JavaScript is everywhere these days-from browser to server to mobile-and now you, too, need to learn the language or dive deeper than you have. This concise book guides you into and through JavaScript, written by a veteran programmer who once found himself in the same position.",
    "website": "http://speakingjs.com/"
  }
]

// Define a basic GET request. The request & response object are passed in
app.get('/', async (req, res) => {
  // Use the request object to send back 'Hello world!'
  res.send('Hello world!');
});

// Define another get with a route afterwards
app.get('/books', async (req, res) => {
  // Use res.json() instead of res.send() to tell express we're returning JSON
  res.json(books);
})

// Define the port we're going to listen for requests on
const port = 3000;

// Tell the app to listen on that port, and log out to the console once its listening.
app.listen(port);
console.log(`listening on http://localhost:${port}`);

Lets test our work. In Postman, add a folder in the NodeJS API collection named Books, and add a new request inside that called Get Books. In the address bar, enter http://localhost:3000/books and click Send. You should get the sample data back.

Reorganizing our Route

Now we have a specific route for books. This is great, but we wont want all of our routes in the index.js file. can get out of hand depending on how many routes you have in the API. Let’s take a moment and move this route into a dedicated file. Create a new folder called routes and a file in that folder called books.js. Start but adding the following code to that file;

const express = require('express');
// Define a new Router object
const router = new express.Router();

// Export the router
module.exports = router;

Now move the new code from **index.js** into here. Change \`app.get\` to \`router.get\`. The completed file should look like this.

const express = require('express');
// Define a new Router object
const router = new express.Router();

// Define some dummy book data, courtesy of https://gist.github.com/nanotaboada/6396437
let books = [{
    "id": 1,
    "isbn": "9781593275846",
    "title": "Eloquent JavaScript, Second Edition",
    "subtitle": "A Modern Introduction to Programming",
    "author": "Marijn Haverbeke",
    "published": "2014-12-14T00:00:00.000Z",
    "publisher": "No Starch Press",
    "pages": 472,
    "description": "JavaScript lies at the heart of almost every modern web application, from social apps to the newest browser-based games. Though simple for beginners to pick up and play with, JavaScript is a flexible, complex language that you can use to build full-scale applications.",
    "website": "http://eloquentjavascript.net/"
  },
  {
    "id": 2,
    "isbn": "9781449331818",
    "title": "Learning JavaScript Design Patterns",
    "subtitle": "A JavaScript and jQuery Developer's Guide",
    "author": "Addy Osmani",
    "published": "2012-07-01T00:00:00.000Z",
    "publisher": "O'Reilly Media",
    "pages": 254,
    "description": "With Learning JavaScript Design Patterns, you'll learn how to write beautiful, structured, and maintainable JavaScript by applying classical and modern design patterns to the language. If you want to keep your code efficient, more manageable, and up-to-date with the latest best practices, this book is for you.",
    "website": "http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/"
  },
  {
    "id": 3,
    "isbn": "9781449365035",
    "title": "Speaking JavaScript",
    "subtitle": "An In-Depth Guide for Programmers",
    "author": "Axel Rauschmayer",
    "published": "2014-02-01T00:00:00.000Z",
    "publisher": "O'Reilly Media",
    "pages": 460,
    "description": "Like it or not, JavaScript is everywhere these days-from browser to server to mobile-and now you, too, need to learn the language or dive deeper than you have. This concise book guides you into and through JavaScript, written by a veteran programmer who once found himself in the same position.",
    "website": "http://speakingjs.com/"
  }
]

// Define another get with a route afterwards
router.get('/', async (req, res) => {
  // Use res.json() instead of res.send() to tell express we're returning JSON
  res.json(books);
})

// Export the router
module.exports = router;

Finally, back in index.js, import the route and register it with the express app. Your index.js should look like this. I’ve commented near the line that registers the route.

// Import express and create a new express app
const express = require('express');
const app = express();
// Import our new books route file
const booksRoute = require('./routes/books')

// Define a basic GET request. The request & response object are passed in
app.get('/', async (req, res) => {
  // Use the request object to send back 'Hello world!'
  res.send('Hello world!');
});

// Register the books routes with /books
app.use('/books', booksRoute);

// Define the port we're going to listen for requests on
const port = 3000;

// Tell the app to listen on that port, and log out to the console once its listening.
app.listen(port);
console.log(`listening on http://localhost:${port}`);

Test the API again to make sure it works.

If you run into any issues following these guides, feel free to jump into my Discord server at https://fullstack.chat to ask me personally for help.

Querying Data

Now our books dataset isn’t that big. But if your making a request to a database that has thousands or millions of records, returning all of the data in one shot is not reasonable, so you need a way to filter that data.

Query parameters are great for this. You can pass in a field and value to filter on by creating a url like so;

http://localhost:3000/books?id=1

The question mark in at the end of the URL is what tells the API that the query parameters are starting. You can chain multiple query parameters together using an ampersand.

http://localhost:3000/books?id=1&pages=472

But of course you’ll need to setup your API to handle this properly. So let’s update our get method in books.js to look like this.

// Define another get with a route afterwards
router.get('/', async (req, res) => {
  // Create an undefined object to return data with
  let responseData;

  // Check if the request objects' query includes a param
  if (req.query.id) {
    // Filter the data for that parameter.
    // Note: Query parameters come in as strings, so we need to cast it to a Number for comparing
    responseData = books.filter(book => book.id === Number(req.query.id));
  }

  // If response data is still undefined, there is no query. Return the whole dataset.
  if (responseData === undefined) {
    responseData = books
  }

  res.json(responseData)
})

Now lets test this out in Postman.

You can also use parts of the path when querying data as well. A common practice is to use the unique Id of an entity in the URL path to request back a single object:

http://localhost:3000/books/1

This would return a book with the Id of 1. To set this up in your API, update the get method as follows.

// Define another get with a route afterwards
router.get('/:id?', async (req, res) => {
  // Create an undefined object to return data with
  let responseData;

  if (req.params.id) {
    responseData = books.find(book => book.id === Number(req.params.id))
  } else {
    // Check if the request objects' query includes a param
    if (req.query.id) {
      // Filter the data for that parameter.
      // Note: Query parameters come in as strings, so we need to cast it to a Number for comparing
      responseData = books.filter(book => book.id === Number(req.query.id));
    }
  }

  // If response data is still undefined, there is no query. Return the whole dataset.
  if (responseData === undefined) {
    responseData = books
  }

  res.json(responseData)
})

Now test it with Postman.

Also worth noting that since we’re requesting back a single item using the URL parameter, we’re returning a single object using the Array.find() method instead of an array of objects.

Sending data to the API

What if we want to send data to an API for storing or processing? We can actually use the same route with a different method to do this. We need to add another library into our API to make handling these requests simple. Open terminal and run npm install body-parser. Once that’s done, add this after importing the booksRoute in index.js;

// Add body-parser middleware
const bodyParser = require('body-parser');
app.use(bodyParser.json());

body-parser is whats called middleware in Express. On a very basic level, it’s a function that sits within the overall flow of the request within express. You can add multiple pieces of middleware in Express to modify the behaviour of the API. In this case, body-parser will parse the incoming request body into a JSON object we can more easily work with.

We’re going to add the following code after the get method in books.js;

// Define a post method which will be used to accept data into the API
router.post('/', async (req, res) => {
  // Add the request body object into the books array
  books.push(req.body);
  // Send a status of 201, which means an item was created
  res.status(201).send();
});

Now back in Postman, lets create a new request;

  1. Create a new request in the Books folder
  2. Set the address to the books URL, but change the method to POST
  3. Click the Body tab
  4. Set the type to Raw
  5. And the formatting to JSON
  6. Add the following to the request body
  7. Click Send
{
    "id": 4,
    "isbn": "1234567890123",
    "title": "My Cool Book"
}

Provided you got a 201 back under Status Code, now we can send the get request and see if the data was added to our books data.

Great! Looks like everything is working correctly for now. We have three issues with the POST method at the moment.

  • We are saving the data in memory, which means in when the API restarts, that data will be lost.
  • We are not validating any incoming data, so literally anything can be sent.
  • We are not authorizing any of the requests, which means anyone can send data to the API.

I’ll be tackling these three issues over the next few articles. The next article in the series will go over how to store data in a database, specifically in AWS using DynamoDB.