News APITutorial

How to Build a Real-Time News App with the Mediastack API (React Tutorial)

Build a Real-Time News App with the Mediastack API

Building a news app that collates a curated list of top headlines based on a user’s personalized requirements is easier than it sounds! Using a news media API integration and React, you can quickly put together a personalized news application that fetches live news headlines and filters them by category, keyword, and source.

In this article, we’ll first answer the question “What is a news API?” then show you how to get news data from websites by leveraging the real-time news data API Mediastack, along with Create React App, to build a dynamic, user-specific experience.

✅ You Will Learn

  • What a news API is and how it works
  • How to filter real-time news headlines by keyword, category, and country
  • How to use the Mediastack API for free
  • How to build a responsive news app with React
  • How to secure API keys using a backend with Express.js
  • How to deploy your news app safely and responsibly

What is a news API?

A news API provides an endpoint to retrieve news headlines, articles, and metadata from multiple sources in real-time. Typically, an API accepts network requests through a standardized web architecture like REST, and returns data in easily-parsed JSON format.

What are the benefits of real-time data in news app?

Custom news feed apps that provide real-time updates are becoming more and more critical in a world where algorithms rule access to content. Sites like Twitter and Facebook no longer prioritize recency over virality – the latest updates are buried under “popular” posts, ads, and videos.

Users are desperate for a filtered news app experience that allows them to easily see the latest stories without having to wade through a sea of irrelevant content.

What makes a good news API?

Any worthwhile API should be fast, reliable, and easy to integrate into your application. Beyond that, a news API should be filterable by language and country, should provide access to a large database of sources, and should never return biased results.

Prerequisites

This real-time news API tutorial assumes familiarity with React and APIs. If you need to brush up on React before starting, check the docs. If you’re unfamiliar with what an API is, have a look at APILayer’s overview. 

Step One: Getting Started With the Mediastack API

The Mediastack API is a scalable JSON API that delivers global news, headlines, and blog articles in real time. It supports over 7500 news sources, and is accessible via a secure REST endpoint.

Signing Up

Mediastack offers a free tier allowing up to 100 requests per month. This should be plenty to get started with a custom news feed API application. Head to https://mediastack.com/signup/free to sign up for your free account.

Get Your Free
News Data API Key!

Join thousands of developers using Mediastack for Global News Data.

Get Your Free API Key!
No Credit Card Required*
100 Requests Free!

Access Key

Once you’ve signed up, you’ll land on your custom news feed API dashboard, where you’ll see your API access key, statistics on monthly usage, and links to documentation. Make a note of your access key (we’ll need it later), and let’s look at the documentation for an overview of the endpoint we’ll use.

Mediastack real-time news API documentation

Endpoint

The Mediastack API has three endpoints: one for live news, one for historical news, and one for sources. For this tutorial, we’ll use the Live News endpoint.

The base URL for the endpoint is:

				
					http://api.mediastack.com/v1/news
				
			

Test Request

First, let’s send a test request to the news API and get familiar with the response. The documentation page provides an easy way to do this, with a “Run API Request” button right next to the endpoint:

example of api request , Highlighting Run test

As you can see from the image above, the request should be sent to the base URL, with your access key included as a query parameter. Any additional parameters, such as keyword and country, are optional, and will filter the response that is returned. See below for a complete list of additional parameters.

Click “Run API Request” to hit the API, and the JSON response will print out into a new browser tab. Hit “Pretty Print” in the top left corner of your browser (if it’s available) and take a look at the result:

Mediastack news API JSON response object

Here, we can see the news data that is returned. First, we get some pagination options, which allow us to load subsequent pages of results. Next, the data array returns a list of news articles, filtered by whichever parameters were provided (in this case, the test request we sent was looking for “tennis” articles in “us”, “gb” and “de”.

Step Two: Filtering News by Category, Source, and Keyword

Let’s send our own request to the API, filtering for the news that we want to see. To do this, we’ll open up a new browser tab and query the free news API directly by typing into the search bar.

To build your query, start with the base URL. Next, add your access key as the first query parameter by putting the “?” symbol, the parameter name (access_key) and then “=” plus your access key:

				
					https://api.mediastack.com/v1/news?access_key=YOUR_ACCESS_KEY
				
			

Then, you can add additional parameters like keyword, country, or language by adding a “&” and the parameter name, then “=” and whatever you’d like to filter by. You can add multiple options as a comma-separated list. For example:

				
					https://api.mediastack.com/v1/news?access_key=YOUR_ACCESS_KEY&keywords=dog,cat,horse&countries=us
				
			

Try sending a few of your own requests by adjusting the keyword, country, category, and date. The complete list of parameters available is listed in the documentation, and includes everything below, plus pagination options.

List of parameters available for the Mediastack free news aggregator API

Step Three: Building a Simple Frontend UI for News Display

Now that we’re familiar with the API, let’s start with our news API content implementation. We’ll be using Create React App to spin up our app.

Install Dependencies

Check that you have Node>=14 installed on your dev machine, and npm version 5.2 or higher. Create a top-level directory for your project called news-app . Then cd into the folder and use npx to install Create React App and spin up the client side application in one command:

				
					$ mkdir news-app
$ cd news-app
$ npx create react-app client

				
			

You’ll be walked through a quick setup wizard. Once you’ve finished, you can start the app by running:

				
					$ cd client
$ npm start 
				
			

Head to localhost:3000 in your browser. You should see the following boilerplate, provided by Create React App:

Basic Create React App startup visualization

We’ll build our personalized news app directly in the app’s main file: App.js. Open the file in your preferred code editor and delete everything between the two <header> tags. Return to the app in the browser  and you should now see a blank gray page.

Build User Input Fields

We want to build a news aggregator application that lets a user search for the live news headlines they are most interested in. For that, we’ll need to provide them with search boxes where they can type what they’re looking for.

For the purposes of this Mediastack tutorial, let’s assume the user wants articles from the US, in English. We’ll give them boxes to input the keywords they want to search for, and we’ll provide a list of categories for them to choose from.

Open App.js in your code editor, and add the following variable to the top of the file:

				
					const CATEGORIES = [
 'General',
 'Business',
 'Entertainment',
 'Health',
 'Science',
 'Sports',
 'Technology'
]

				
			

Next, add the following JSX to the return function:

				
					       <h1><span class="ez-toc-section" id="Search_Todays_Headlines"></span>Search Today's Headlines<span class="ez-toc-section-end"></span></h1>
       <h2><span class="ez-toc-section" id="News_for_new_DatetoDateString"></span>News for {new Date().toDateString()}<span class="ez-toc-section-end"></span></h2>
       <div className='container'>
         <div className='box'>
           <p className='title'>Categories</p>
           <fieldset>
             <legend>Choose multiple:</legend>
             {
               CATEGORIES.map((category) => {
                 return (
                   <div>
                     <input type="checkbox" id={category} name={category} value={category}/>
                     <label for={category}>{category}</label>
                 </div>
                 )
               })
             }
           </fieldset>
         </div>
         <div className='box'>
           <p className='title'>Keywords</p>
           <fieldset id='keywords'>
             <label for='keywords'>Enter keywords as a comma-separated list.</label>
             <input type='text' placeholder='i.e. tennis,dogs,politics' id='textbox'/>
           </fieldset>
         <button className='button'>Search</button>
         </div>
       </div>
				
			

Let’s break down what’s here.

Headers

Just a simple h1 and h2 to welcome the user to the app and let them know what we’re doing. We’re using the basic Javascript Date object to format and display today’s date.

Divs

Just some containers to lay things out nicely. See the CSS at the end of the article to understand how the style is being applied to space things on the page.

Checkboxes

We’ve created a set of checkboxes to present the user with the categories that the API makes available. We’re iterating over the CHECKBOXES variable we created using the map function, and presenting each option as a checkbox. The categories we’re presenting here were pulled directly from the API documentation.

Text Input

Next, we’ve provided a textbox for the user to input their keywords, with some instructions to add keywords as a comma-separated list.

Button

This is the button the user will click when they’ve input all the keywords and categories they’re looking for and are ready to search. When we’re ready, we’ll hook the onClick handler of this button up to a function that sends our API request.

Here’s what it looks like in the UI:

Basic personalized news application UI

Capture User Input

Right now, these inputs don’t do anything. We need to hook up their onChange handlers to some functions that capture what the user enters, and put that information into state. Then, we can grab that data from state and pass it to our API request as parameters.

Import the useState hook from React and use it to set up some empty state variables.

				
					import { useState } from 'react';


...
// in App component
 const [keywords, setKeywords] = useState('');
 const [categories, setCategories] = useState('');
				
			

Next, create some handler functions that capture the user’s input and store it in these values. For keywords, this is as simple as attaching the setKeywords state updater we just created to the text input:

				
					<input
id='textbox'
type='text' 
placeholder='i.e. tennis,dogs,politics' 
onChange={(e) => { setKeywords(e.target.value) }} //added this
/>

				
			

This will set everything the user enters into the textbox into state.

For categories, things are a little more complicated. We need a function that creates a string from the values the user selects from the checkboxes. We also need to handle the case that a user unselects a checkbox, in which case, we would remove that checkbox’s value from the string.

				
					const updateCategories = (e) => {
   let categoryString = '';
   const selectedCategory = e.target.value;
   // see if the selected category already appears in the string
   // (will return -1 if not)
   const index = categories.indexOf(selectedCategory);


   if ( index !== -1) {
     // the selected category appears in the string
     // remove it (and the comma that follows it)
     categoryString =
categories.substring(0, index) + 
categories.substring(index + selectedCategory.length + 1, categories.length);
   } else {
     // the selected category was not found in the string
     // add it (first check if we need a comma)
     categoryString = categories.length ? 
categories + ',' + selectedCategory.toLowerCase() : 
selectedCategory.toLowerCase();
   }
   // update the state with the new string
   setCategories(categoryString);
 }

				
			

We use indexOf to determine if the chosen category has already been added to our categories list. If it has, we remove it by creating a new string out of two substrings – one of the original list up to the index where the new category appears, and one of the original list starting at the index right after the category appears (the comma.) This splices the category and its trailing comma out of the string.

If the category doesn’t appear in the categories list, we simply add it, first checking whether this is the first category that’s been added to the list. If it is the first, we just set the list to that. If not, we append it after a comma. We use toLowerCase because the API expects lowercase strings only, but we like to display our labels with uppercase first letters. 

Hook up this function to the onChange handler of your checkbox:

				
					<input type="checkbox" id={category} name={category} value={category} onChange={updateCategories}/>
				
			

Send User Input as Parameters

Now we’re ready to start on the media API integration by sending our API request, passing the two strings we created in state as query parameters. We’ll write the function, then hook the function up to our Search button, so the request is sent when the user hits Search.

Note: we include the access key below because this is a Mediastack tutorial app, running in development only, and we will not be deploying this app to production. 

It is never safe to store secret keys in client side code, even when using gitignore and a .env file. The values are embedded into the build, and can be viewed using developer tools in the browser. If you want to deploy your app to production, you will need to create a server using something like Express.js, store the API key on the backend, and route your frontend requests there. See “Next Steps” for information on how to do this.

				
					 const searchAPI = async () => {
   const API_URL = 'https://api.mediastack.com/v1/news';
   const API_KEY = 'YOUR ACCESS KEY';
   const constants = '&languages=en&countries=us';
   const queryParams = `?access_key=${API_KEY}&categories=${categories}&keywords=${keywords}`;


   try {
     const response = await fetch(API_URL + queryParams + constants);
     const json = await response.json();
     const data = json.data;
     console.log(data);
   } catch(error) {
     console.error(error);
   }
 }


...
// hook the function up to the button
<button className='button' onClick={searchAPI}>Search</button>

				
			

Here, we’re constructing the complete live news headlines URL by combining our base URL with the constants we decided to impose on our user (English language, US articles only) and the search parameters  the user entered. We send the request, wrapped in a try/catch block to handle errors.

For now, we’re just logging the data we receive to the console. In the next step, we’ll add that data to state and display it in the UI. Take a look at the console to make sure you’re getting back what you expect. It should be an array of objects:

live news headlines API response

Display News Articles

It’s time to render that news data to the UI. To do this, we’ll add the data to state, then create an unordered list and iterate over the array in state, rendering each item to the UI as a list item.

First, create a variable in state. Then, replace the console.log with the setArticles function, and pass it the data array we received from the API.

				
					 const [articles, setArticles] = useState([]);
...
// inside searchAPI
 const data = json.data;
 setArticles(data);
				
			

Next, we’ll add the following JSX to iterate over that array and build our list:

				
					<div>
           <ul>
           {articles.map((article, i) => {
             return (
               <li key={i} id='article'>
                 <h4>{article.title}</h4>
                 <i id='description'>{article.description}</i>
                 &nbsp;<a href={article.url} id='link'>Read More</a>
               </li>
             )
           })}
         </ul>
       </div>
				
			

Check out the CSS at the end of this article to see how things are being organized on the page. Now, when the user clicks Search, the response from the custom news feed API will render on the page. Here’s what that looks like:

Sample of the news

Nice! Let’s take a look at the app in action.

Filtered news application in action

Next Steps: Proxy to a Backend for Deploy

If you plan to deploy your app to production, you’ll need to store your secret API key on a backend, and proxy your API requests to it. You can spin up a backend fairly easily using Express.

Create a Server File

At the beginning of this tutorial, we created a top-level directory called news-app. Run the following terminal commands to navigate back to that directory now, initialize it as a Node project, and create a folder for your server code:

				
					$ cd ../
$ npm init -y
$ mkdir server
				
			

Next, we’ll navigate into our server folder, install Express (a handy package for quickly setting up a server), and create an index.js file, which will be the file that runs our server:

				
					$ cd server
$ npm i -s express
$ touch index.js
				
			

Your app structure should now look like this:

				
					news-app/
  - server/
	- index.js
  - client/
	- src/
	  - App.js
	  ...
				
			

Open the index.js file in your code editor and add the following:

				
					const express = require("express");


const PORT = process.env.PORT || 3001;


const app = express();


app.listen(PORT, () => {
 console.log(`Server listening on ${PORT}`);
});

				
			

Then, in your terminal, run:

				
					$ node index.js
				
			

And verify that the backend is running by viewing the string that prints to the terminal console. Note that the backend is running on port 3001, while the frontend is running on port 3000: two separate parts of the same app are now running on two different ports that talk to each other.

You can visit the backend in your browser at localhost:3001, but all you’ll see is:

Cannot get

Create a Dotenv File

To store your API access key securely, we’ll install Dotenv, create a .env file in the server folder and add the access key to it. Then, you can access the key in your backend requests using process.env. Run the following in your top-level directory:

				
					$ npm install -s dotenv
$ cd server
$ touch .env

				
			

This will add the dependency and create the file. In the file, let’s add our access key and API URL:

				
					API_ACCESS_KEY="your access key"
API_URL="https://api.mediastack.com/v1/news"

				
			

Then, in index.js, initialize Dotenv at the top of the file, and you’ll be able to grab the key and URL values from process.env:

				
					require('dotenv').config()
const URL = process.env.API_URL + `?access_key=${process.env.API_ACCESS_KEY}`;
				
			

Finally, create a .gitignore file in the root of your project, and add the .env file to it. This tells git to ignore the file during a commit, so that file will never be pushed to an online repository. You’ll then set your environment variables manually, using the interface provided by your deployment platform.

Proxy Frontend Requests

Now, we’ll create a route on our server that intercepts API requests from the frontend, attaches the API key, sends those requests on to the API, and then returns the API response to the client. Let’s call this route api. Create the route in server/index.js like so:

Right now, this doesn’t return anything. We want to move the logic of querying the API from our frontend to this function. The frontend passes us the query parameters as an object, but the API expects a string, so we’ll parse the parameters out of the query value that Node gives us, then stringify them using the QueryString node module, and append them to our URL in fetch:

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

				
			

Right now, this doesn’t return anything. We want to move the logic of querying the API from our frontend to this function. The frontend passes us the query parameters as an object, but the API expects a string, so we’ll parse the parameters out of the query value that Node gives us, then stringify them using the QueryString node module, and append them to our URL in fetch:

				
					app.get("/api", async (req, res) => {
   const searchParameters = req.query;
   const queryString = QueryString.stringify(searchParameters);
   const response = await fetch(URL + `&${queryString}`);
   const json = await response.json();
   res.json(json);
});

				
			

Add the following line to the package.json file of your frontend code:

				
					 "proxy": "http://localhost:3001",
				
			

This will allow us to send requests to our Node backend without running into any CORS (Cross-Origin Request) errors. You’ll need to restart the frontend development server for this change to take effect.

Now we can update the api call on the frontend to hit our server instead of the Mediastack API.

				
					 const searchAPI = async () => {
   const API_URL = '/api';
   const constants = '&languages=en&countries=us';
   const queryParams = `?categories=${categories}&keywords=${keywords}`;


   try {
     const response = await fetch(API_URL + queryParams + constants);
     const json = await response.json();
     const data = json.data;
     setArticles(data);
   } catch(error) {
     console.error(error);
   }
 }

				
			

Notice how our API_URL is now just the backend route that handles the request for us. We’re no longer appending an API key, either. All we’re sending are the parameters that the user provided. The server is handling the rest.

If you go back to the browser now and run the request, you can take a look at the Network tab in the console and see that the request is indeed going to our server. You can also examine the response that the server sends back.

the request is indeed going to our server

Add Loading State

You’ll notice the response takes a bit longer to come back now. We’d like to let our user know that the app is working while this request to the free news API is processing, so let’s add some loading state to the button after it’s clicked. We’ll need to create a value in state to indicate when the button should be loading and when it finishes.

				
					 const [loading, setLoading] = useState(false);


...
// in searchAPI
     setLoading(true); //added this
     const response = await fetch(API_URL + queryParams + constants);
     const json = await response.json();
     const data = json.data;
     setArticles(data);
     setLoading(false); //added this


...
// in JSX
<button className='button' onClick={searchAPI}>{loading ? "Loading" : "Search"}</button>
				
			

Now, when the user clicks the button, they’ll see the word “Loading” instead of “Search” as long as we’re waiting for the API call.

From here, if you wanted to deploy your app via Netlify, Github pages, or S3, you could do it securely without worrying about exposing your API access key.

Complete Code

Here’s the code for the complete frontend and backend of the application.

client/App.js

				
					import './App.css';
import { useState } from 'react';


const CATEGORIES = [
 'General',
 'Business',
 'Entertainment',
 'Health',
 'Science',
 'Sports',
 'Technology'
]


function App() {


 const [keywords, setKeywords] = useState('');
 const [categories, setCategories] = useState('');
 const [articles, setArticles] = useState([]);
 const [loading, setLoading] = useState(false);


 const updateCategories = (e) => {
   let categoryString = '';
   const selectedCategory = e.target.value.toLowerCase();
   // see if the selected category already appears in the string
   // (will return -1 if not)
   const index = categories.indexOf(selectedCategory);


   if ( index !== -1) {
     // the selected category appears in the string
     // remove it (and the comma that follows it)
     categoryString = categories.substring(0, index) + categories.substring(index + selectedCategory.length + 1, categories.length);
   } else {
     // the selected category was not found in the string
     // add it (first check if we need a comma)
     categoryString = categories.length ? categories + ',' + selectedCategory : selectedCategory;
   }
   // update the state with the new string
   setCategories(categoryString);
 }


 const searchAPI = async () => {
   const API_URL = '/api';
   const constants = '&languages=en&countries=us';
   const queryParams = `?categories=${categories}&keywords=${keywords}`;


   try {
     setLoading(true);
     const response = await fetch(API_URL + queryParams + constants);
     const json = await response.json();
     const data = json.data;
     setArticles(data);
     setLoading(false);
   } catch(error) {
     console.error(error);
     setLoading(false);
   }
 }


 return (
   <div className="App">
     <header className="App-header">
       <h1><span class="ez-toc-section" id="Search_Todays_Headlines-2"></span>Search Today's Headlines<span class="ez-toc-section-end"></span></h1>
       <h2><span class="ez-toc-section" id="News_for_new_DatetoDateString-2"></span>News for {new Date().toDateString()}<span class="ez-toc-section-end"></span></h2>
       <div className='container'>
         <div className='box'>
           <p className='title'>Categories</p>
           <fieldset>
             <legend>Choose multiple:</legend>
             {
               CATEGORIES.map((category) => {
                 return (
                   <div>
                     <input type="checkbox" id={category} name={category} value={category} onChange={updateCategories} />
                     <label for={category}>{category}</label>
                 </div>
                 )
               })
             }
           </fieldset>
         </div>
         <div className='box'>
           <p className='title'>Keywords</p>
           <fieldset id='keywords'>
             <label for='keywords'>Enter keywords as a comma-separated list.</label>
             <input type='text' placeholder='i.e. tennis,dogs,politics' onChange={(e) => {setKeywords(e.target.value)}} id='textbox'/>
           </fieldset>
         <button className='button' onClick={searchAPI}>{loading ? "Loading" : "Search"}</button>
         </div>
       </div>
       <div>
           <ul>
           {articles.map((article, i) => {
             return (
               <li key={i} id='article'>
                 <h4>{article.title}</h4>
                 <i id='description'>{article.description}</i>
                 &nbsp;<a href={article.url} id='link'>Read More</a>
               </li>
             )
           })}
         </ul>
       </div>
     </header>
   </div>
 );
}


export default App;

				
			

client/App.css

				
					.App {
 text-align: center;
}


.App-header {
 background-color: #282c34;
 min-height: 100vh;
 display: flex;
 flex-direction: column;
 align-items: center;
 justify-content: center;
 font-size: calc(10px + 2vmin);
 color: white;
}


.container {
 display: flex;
 justify-content: space-between;
 width: 80%;
}


.box {
 padding: 2rem;
}


.title {
 border: 1px solid gray;
 padding: 1rem;
}


#keywords {
 display: flex;
 flex-direction: column;
}


#textbox {
 padding: 1rem;
 margin-top: 1rem;
}


.button {
 width: 100%;
 padding: 1rem;
 margin-top: 3rem;
}


#article {
 list-style: none;
 text-align: left;
 margin-top: 5rem;
 padding: 0 0 2rem 2rem;
 border: 1px solid black;
}


#description {
 color: gray;
}


#link {
 color: lightblue;
}

				
			

client/package.json

				
					 "proxy": "http://localhost:3001", //added this line
				
			

server/index.js

				
					const express = require("express");
require('dotenv').config()
const URL = process.env.API_URL + `?access_key=${process.env.API_ACCESS_KEY}`;


const PORT = process.env.PORT || 3001;


const app = express();


app.get("/api", async (req, res) => {
   const searchParameters = req.query;
   const response = await fetch(URL, searchParameters);
   const json = await response.json();
   res.json(json);
});


app.listen(PORT, () => {
 console.log(`Server listening on ${PORT}`);
});

				
			

server/.env

				
					API_ACCESS_KEY="your key"
API_URL="https://api.mediastack.com/v1/news"
				
			

Mediastack: A Free News Aggregator API

This tutorial on how to build a news API for developers should have given you a basic understanding of how to use a media API integration to build a personalized news application. From here, take a look at the Netlify documentation to learn how to deploy your app to production, or check out other APILayer APIs for developers.

FAQs 

How to get news data from websites?

There are many free news APIs, such as Mediastack, that allow you to make requests to their endpoint for free, up-to-the-minute headlines that you can use in your own personalized news application.

How to use Mediastack API?

It’s very easy to sign up for a free news API account with Mediastack – just head to their website, where you can select their free tier, which gives you up to 100 requests for free every month. To query their endpoint, just send a GET request containing your access key, and you’ll receive back a paginated list of up-to-date news articles with title, author, description, and link.

How do I build a news app using React and an API?

You can use Create React App to build the frontend and use a news API like Mediastack to fetch data. This tutorial shows how to add search inputs, filters, and live rendering of articles.

Is Mediastack API free to use?

Yes — the free plan offers 100 monthly requests. You can upgrade for more volume, HTTPS support, and access to historical news endpoints.

What’s the best way to filter news by keyword or category?

Use a news API that supports query parameters. With Mediastack, you can filter by keywords, countries, languages, and categories to personalize the results.

What’s the difference between using a news API and web scraping?

News APIs like Mediastack are faster, legal, and easier to maintain. Scraping news sites can lead to blocks or legal issues, whereas APIs give you structured data instantly.

Stay Connected​

Related posts
News API

How to Build a No-Code Searchable News App with the Mediastack API and Lovable

TutorialWeb Scraping System

How to Build a Scalable Web Scraping System Using Scrapestack API (2025 Guide)

TutorialWeatherstack API

How to Build a Real-Time Weather Dashboard Using the Weatherstack API (Step-by-Step)

API for DeveloperTutorial

Import Data From API to Google Sheets: Step-by-Step Guide

Leave a Reply

Your email address will not be published. Required fields are marked *