Editor’s Note: This post was reviewed for accuracy on 12 April 2023. Node.js recently released v20, which includes the new Node.js Permission Model, a synchronous import.meta.resolve
, v11.3 of the V8 JavaScript engine, and a stable test_runner
module. Check out the docs for more information.
When building an API with Node.js, there are different options available regarding authentication. Some of these include the JSON Web Token, OAuth, the API key, and more. In this article, we’ll learn how to authenticate a Node.js API using API keys.
Using API keys has an advantage if you want to set a limit or track how often a particular user is using an API. By using API keys, the user doesn’t need to worry about multi-factor authentication with their username and password. Your API’s user will be able to automate data fetching on the application.
In this tutorial, we’ll create an API with Node.js. Then, we’ll create an authentication system that creates an API key whenever a user registers on the application. With the newly created API key, the user will be able to access all of the routes on the API.
You can find the complete code on GitHub. To follow along with this article, you’ll need:
First, we’ll handle all the installation and initial setup to run our application. We’ll use Express to develop the API and Nodemon to run the API server and listen for changes in the code in real-time.
We’ll have to install them, but first, create a folder for your project and run the following command to create a package.json
file for your project:
$ npm init -y
Now, update the "scripts"
module in your package.json
file with the code below so that we’ll be able to run the server with Nodemon:
... "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "nodemon Server.js" } ...
Later in the article, we’ll create the Server.js
file where we’ll run the server from. Now, install Nodemon and Express by running the following command:
npm install express nodemon
The authentication system takes in a given username and creates user data, containing the username, API key, and a count of usage on a particular day. We’ll need the count so that we can set a limit on how many times a user can use the API on a particular day.
We’ll start by creating a function called genAPIKey()
that generates the API when a new user is created. The function will generate a base-36
string that contains 30 characters within A-Z
and 0-9
, which will represent the API key. You can start by creating a new JavaScript file called apiAuth.js
and pasting the following code:
const genAPIKey = () => { //create a base-36 string that contains 30 chars in a-z,0-9 return [...Array(30)] .map((e) => ((Math.random() * 36) | 0).toString(36)) .join(''); };
Next, we’ll develop a function that creates the user data when a username is entered. We‘ll store this new user in an array, so we need some initial data to get started. Create a new file called initialData.js
and paste the following code:
const users = [ { _id: 1587912, api_key: "rwuy6434tgdgjhtiojiosi838tjue3", username: "username", usage: [{ date: "2022-10-10", count: 17 }], }, ]; const Countries = [ { _id: 1, name: "Nigeria" }, { _id: 2, name: "China" }, ]; module.exports = { users, Countries };
Now, we’ll develop the function that creates a user by pasting the following code in the apiAuth.js
file:
const users = require('./initialData').users; // import initial data ... const createUser = (_username, req) => { let today = new Date().toISOString().split('T')[0]; let user = { _id: Date.now(), api_key: genAPIKey(), username: _username, usage: [{ date: today, count: 0 }], }; console.log('add user'); users.push(user); return user; };
Next, we’ll develop a function that will authenticate the API key so that you can access specified parts of the API. This function will compare the registered API key with the x-api-key
, which will come with the request.
If the request goes through, the count per day increases, and if you’ve reached your max request per day, you’ll receive an error. Paste the following code into your apiAuth.js
file:
const authenticateKey = (req, res, next) => { let api_key = req.header("x-api-key"); //Add API key to headers let account = users.find((user) => user.api_key == api_key); // find() returns an object or undefined if (account) { //If API key matches //check the number of times the API has been used in a particular day let today = new Date().toISOString().split("T")[0]; let usageCount = account.usage.findIndex((day) => day.date == today); if (usageCount >= 0) { //If API is already used today if (account.usage[usageCount].count >= MAX) { //stop if the usage exceeds max API calls res.status(429).send({ error: { code: 429, message: "Max API calls exceeded.", }, }); } else { //have not hit todays max usage account.usage[usageCount].count++; console.log("Good API call", account.usage[usageCount]); next(); } } else { //Push todays's date and count: 1 if there is a past date account.usage.push({ date: today, count: 1 }); //ok to use again next(); } } else { //Reject request if API key doesn't match res.status(403).send({ error: { code: 403, message: "You not allowed." } }); } }; module.exports = { createUser, authenticateKey };
We’re exporting so that the Server.js
can use these functions.
In this section, we’ll create the routes that we’ll use to access the data in the API while applying the API key checks. We’ll create endpoints to register a user, add countries to the country list, and also get the list of countries. Requests to add or get countries will require API key authentication.
To get started, create a new file called Server.js
and paste the code below. The code contains imports that we’ll use later on, as well as what will be outputted on the homepage:
const express = require('express'); const app = express(); const port = 4000; const API = require('./apiAuth'); // Get initial data for users and countries const { users, Countries } = require('./initialData'); //handle json body request app.use(express.json()); app.get('/', (req, res) => { //home page res.status(200).send({ data: { message: 'You can get list of countires at /api/country.' } }); });
Using the createUser()
function we developed earlier, we’ll create a route that will add new users to the user list and generate the user data. Paste the following code in your Server.js
file:
... app.post('/api/register', (req, res) => { //create a new with "user:Username" let username = req.body.username; let user = API.createUser(username, req); res.status(201).send({ data: user }); });
Now, we’ll develop the routes to create and get countries. In this route, we’ll include the authenticateKey()
function we developed earlier so that the API key sent in the header of the request can be authenticated and the request can be counted. Then, we’ll finally set the port that the server should listen on. Paste the following code in your Server.js
file:
app.get('/api/country', API.authenticateKey, (req, res) => { //get list of all Countries let today = new Date().toISOString().split('T')[0]; console.log(today); res.status(200).send({ data: Countries, }); }); app.post('/api/country', API.authenticateKey, (req, res) => { //add a new country let country = { _id: Date.now(), name: req.body.country, }; Countries.push(country); res.status(201).send({ data: country, }); }); app.listen(port, function (err) { if (err) { console.error('Failure to launch server'); return; } console.log(`Listening on port ${port}`); });
Now, we’re done with the coding part of this tutorial, and we can move into testing. We’ll test the API endpoints with cURL. Before testing, open up your terminal and run the following command to start the server:
npm run dev
Open another terminal window and run the following command to create a new user. Once the command is run, you’ll be provided with some user data, one of which includes the API key:
curl -d "user:User1" -X POST http://127.0.0.1:4000/api/register -w "\n"
Now that you have created a user, you can use the country endpoints. Let’s try to retrieve the countries that we have on the list using the API key that we were provided with. You can do so by running the command below. Make sure you replace 2agp59nwu8nrszm4p6kfriekoeo0s1
in the command below with your own API key:
curl http://127.0.0.1:4000/api/country -H "x-api-key: 2agp59nwu8nrszm4p6kfriekoeo0s1" -w "\n"
In this tutorial, we created an API with Node.js and developed an authentication system that generates an API key whenever a user is registered. With the newly created API key, the user is able to access all the routes on the API and API usage can be tracked.
You can build upon the knowledge you have obtained in this article by adding API key authentication to your Node.js API that uses a database to store data. In addition to user authentication, you can use the API key for the advantages that were mentioned in the introductory section of this article.
Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third-party services are successful, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.
LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.