For developers, APIs are a crucial tool for connecting our projects with the wider world, but we have to be smart about how we use them. APIs that are free to use often come with limits on the number of calls that can be made within a certain period, and reckless use of paid APIs can rack up hefty fees before you know it. (Did anyone else here accidentally create an infinite loop of GET requests?) It pays to have a thoughtful approach to making API calls and one tool that can help is localStorage
. It's a property that allows JavaScript to store key-value pairs directly in the browser. This information persists even when a user leaves the website or closes the browser, making localStorage
a great way to store any nonsensitive data returned by APIs as a means of reducing repeat calls.
One of the best parts of localStorage is that it's already built into your web app. Don't believe me? Open your browser's developer tools right now and enter localStorage
into the console. You'll see the information this website is storing in your browser. You can try this with your own projects, too, though if you're not using localStorage yet it will just return null
. The key point here is that it won't be undefined - localStorage
is just sitting there waiting for you to use it.
The process of adding a value to localStorage
will look straightforward to anyone familiar with key-value pairs. Simply use the setItem()
method and pass it two arguments: a key and a value. In the example below, I have information stored in a variable named info
, and I want to store it under the matching key of "info."
localStorage.setItem("info", JSON.stringify(info));
That's all it takes. Because localStorage
can only store strings, you have to use JSON.stringify()
on the value before it is passed to setItem()
. From there, the information is stored, waiting to be retrieved whenever you want it. The process of accessing localStorage
looks like this:
JSON.parse(localStorage.getItem("info"))
Use localStorage.getItem()
to return the stored value. This method takes the key as its lone argument. Since we stringified the information before storing it, we need to wrap the getItem()
call in JSON.parse()
to make sure we get back our information in its original format.
I encourage you to try this out in the console right now. Set whatever key and value you'd like and then retrieve it using getItem()
. For bonus points, look up the value of localStorage
afterward and find your key-value pair inside of the objects returned by the console.
This tool lets us store the results of an API call directly in the browser so that we may avoid making repeat calls whenever a user returns to our site. One of my favorite uses is to combine localStorage
with JavaScript's Date
objects to control how frequently a client can make API calls. This provides a balance between relevant, updated data and making too many calls in a short time. A perfect example of this is using a weather API. Many of the best weather APIs have a free tier for personal project use, but you have to be careful not to exceed the usage limits. I like to store the returned weather data in localStorage
, along with a timestamp which lets the app decide if it should use the stored data or fetch a new weather forecast. Let's work through an implementation of this feature.
First I'll give a quick primer on using Date
objects to create time-based conditionals in JavaScript. It all starts with initializing a variable to equal a new Date
instance.
const now = new Date()
From there, you have a wide variety of methods to use on your shiny new Date
instance. We'll be most concerned with getTime()
. This returns the number of miliseconds that passed between the UNIX epoch on midnight UTC, January 1, 1970, and the creation of that particular Date
object instance.
now.getTime()
1703084271885
This may seem like an odd way to report the time, but for programmers, it is quite useful as it allows us to perform math on lengths of time without needing to carry over between the many different units we commonly use. For example, if we want to know how much time has elapsed between two events in our code, we can just subtract one getTime() value from the other and convert the result to whichever unit of time we want.
const later = new Date()
later.getTime() - now.getTime()
183454
183454 / 60000
3.0575666666666668
In this case, I divided by 60,000 milliseconds to convert to minutes and found that just over three minutes had passed. Timestamps like these can be included in the data you save in localStorage
as a way to determine if it's better to make a new API call or if it's fine to use the data you've already stored.
Now we're ready to bring everything together for time-conscious storage and retrieval of API data. In this example, I'm going to assume familiarity with React, including the useState
and useEffect
hooks. Here's the code, I'll go over key details below.
const CACHE_DURATION_MINUTES = 10
const [weather, setWeather] = useState(null)
useEffect(() => {
const cachedWeather = JSON.parse(localStorage.getItem("weather"))
const now = new Date().getTime()
if (!cachedWeather ||
((now - cachedWeather.time) > (60000 * CACHE_DURATION_MINUTES))) {
fetch(weatherFetchLink)
.then(res => res.json())
.then(data => {
setWeather(data)
const newNow = new Date()
const weatherObject = {"data": data, "time": newNow.getTime()}
localStorage.setItem("weather", JSON.stringify(weatherObject) )
})
} else {
setWeather(cachedWeather.data)
}
}, [])
For this example, imagine a web app that includes, among other things, the weather for a particular location. This location data would likely be included as a parameter in the fetch URL for the weather API, which I've just called weatherFetchLink
. I started by declaring a variable up top to determine the time interval between fetches.
const CACHE_DURATION_MINUTES = 10
const [weather, setWeather] = useState(null)
I used 10 minutes. It may seem like a short interval, but I live in New England and I've seen the weather here turn a lot quicker than that. I also establish a state variable to store the weather data for this React component. The useEffect
hook lets me execute a callback function whenever the page first loads by passing an empty dependency array as its second argument (note the empty array on the last line of the full example.)
Within the function passed to useEffect
is the logic that drives our example. First we check to see what, if anything is stored under the key "weather" in localStorage.
const cachedWeather = JSON.parse(localStorage.getItem("weather"))
We can use this value, along with a recent timestamp to create a conditional statement.
if (!cachedWeather ||
((now - cachedWeather.time) > (60000 * CACHE_DURATION_MINUTES)))
If there is no cached weather data, or if that data is more than 10 minutes old, we'll go ahead and fetch new data from the API. The fetch itself is pretty standard in our example, but we did a few new things after setting state for weather
:
const newNow = new Date()
const weatherObject = {"data": data, "time": newNow.getTime()}
localStorage.setItem("weather", JSON.stringify(weatherObject)
We started by creating a new timestamp, then created a new weather object that contains the data returned from our API call as well as the timestamp. This is everything we'll need next time the user returns to decide if we can reuse the stored data.
In the event that the information in localStorage
is less than ten minutes old, we simply update the weather state variable with the related data.
setWeather(cachedWeather.data)
So there you have it, an implementation of localStorage
that either makes an API call or uses stored data depending on the time elapsed since the last call. Remember that localStorage
stores values as strings, so avoid storing any sensitive data - any other users of your machine will be able to see it. Good luck and have fun trying it yourself!