#TL;DR
Here is the REST API Documentation in Postman.
#News
#December 10th, 2020
Added 3 new calculated fields:
- confirmed_daily.
- deaths_daily.
- recovered_daily.
#September 10th, 2020
- Let me know what you think in our topic in the community forum.
- Fixed a bug in my code which was failing if the IP address wasn't collected properly.
#Introduction
Recently, we built the MongoDB COVID-19 Open Data project using the dataset from Johns Hopkins University (JHU).
There are two big advantages to using this cluster, rather than directly using JHU's CSV files:
- It's updated automatically every hour so any update in JHU's repo will be there after a maximum of one hour.
- You don't need to clean, parse and transform the CSV files, our script does this for you!
The MongoDB Atlas cluster is freely accessible using the user readonly
and the password readonly
using the connection string:
1 mongodb+srv://readonly:readonly@covid-19.hip2i.mongodb.net/covid19
You can use this cluster to build your application, but what about having a nice and free REST API to access this curated dataset?!

#COVID-19 REST API
Here is the REST API Documentation in Postman.
You can use the button in the top right corner Run in Postman to directly import these examples in Postman and give them a spin.

One important detail to note: I'm logging each IP address calling this REST API and I'm counting the numbers of queries per IP in order to detect abuses. This will help me to take actions against abusive behaviours.
Also, remember that if you are trying to build an application that helps to detect, understand or stop the spread of the COVID-19 virus, we have a FREE MongoDB Atlas credit program that can help you scale and hopefully solve this global pandemic.
#But how did I build this?
Simple and easy, I used the MongoDB Realm 3rd party HTTP service to build my HTTP webhooks.
Each time you call an API, a serverless JavaScript function is executed to fetch your documents. Let's look at the three parts of this function separately, for the Global & US webhook (the most detailed cllection!):
- First, I log the IP address each time a webhook is called. I'm using the IP address for my
_id
field which permits me to use an upsert operation.
1 function log_ip(payload) { 2 const log = context.services.get("pre-prod").db("logs").collection("ip"); 3 let ip = "IP missing"; 4 try { 5 ip = payload.headers["X-Envoy-External-Address"][0]; 6 } catch (error) { 7 console.log("Can't retrieve IP address.") 8 } 9 console.log(ip); 10 log.updateOne({"_id": ip}, {"$inc": {"queries": 1}}, {"upsert": true}) 11 .then( result => { 12 console.log("IP + 1: " + ip); 13 }); 14 }
- Then I retrieve the query parameters and I build the query that I'm sending to the MongoDB cluster along with the projection and sort options.
1 function isPositiveInteger(str) { 2 return ((parseInt(str, 10).toString() == str) && str.indexOf('-') === -1); 3 } 4 5 exports = function(payload, response) { 6 log_ip(payload); 7 8 const {uid, country, state, country_iso3, min_date, max_date, hide_fields} = payload.query; 9 const coll = context.services.get("mongodb-atlas").db("covid19").collection("global_and_us"); 10 11 var query = {}; 12 var project = {}; 13 const sort = {'date': 1}; 14 15 if (uid !== undefined && isPositiveInteger(uid)) { 16 query.uid = parseInt(uid, 10); 17 } 18 if (country !== undefined) { 19 query.country = country; 20 } 21 if (state !== undefined) { 22 query.state = state; 23 } 24 if (country_iso3 !== undefined) { 25 query.country_iso3 = country_iso3; 26 } 27 if (min_date !== undefined && max_date === undefined) { 28 query.date = {'$gte': new Date(min_date)}; 29 } 30 if (max_date !== undefined && min_date === undefined) { 31 query.date = {'$lte': new Date(max_date)}; 32 } 33 if (min_date !== undefined && max_date !== undefined) { 34 query.date = {'$gte': new Date(min_date), '$lte': new Date(max_date)}; 35 } 36 if (hide_fields !== undefined) { 37 const fields = hide_fields.split(','); 38 for (let i = 0; i < fields.length; i++) { 39 project[fields[i].trim()] = 0 40 } 41 } 42 43 console.log('Query: ' + JSON.stringify(query)); 44 console.log('Projection: ' + JSON.stringify(project)); 45 // [...] 46 };
- Finally, I build the answer with the documents from the cluster and I'm adding a
Contact
header so you can send us an email if you want to reach out.
1 exports = function(payload, response) { 2 // [...] 3 coll.find(query, project).sort(sort).toArray() 4 .then( docs => { 5 response.setBody(JSON.stringify(docs)); 6 response.setHeader("Contact","devrel@mongodb.com"); 7 }); 8 };
Here is the entire JavaScript function if you want to copy & paste it.
1 function isPositiveInteger(str) { 2 return ((parseInt(str, 10).toString() == str) && str.indexOf('-') === -1); 3 } 4 5 function log_ip(payload) { 6 const log = context.services.get("pre-prod").db("logs").collection("ip"); 7 let ip = "IP missing"; 8 try { 9 ip = payload.headers["X-Envoy-External-Address"][0]; 10 } catch (error) { 11 console.log("Can't retrieve IP address.") 12 } 13 console.log(ip); 14 log.updateOne({"_id": ip}, {"$inc": {"queries": 1}}, {"upsert": true}) 15 .then( result => { 16 console.log("IP + 1: " + ip); 17 }); 18 } 19 20 exports = function(payload, response) { 21 log_ip(payload); 22 23 const {uid, country, state, country_iso3, min_date, max_date, hide_fields} = payload.query; 24 const coll = context.services.get("mongodb-atlas").db("covid19").collection("global_and_us"); 25 26 var query = {}; 27 var project = {}; 28 const sort = {'date': 1}; 29 30 if (uid !== undefined && isPositiveInteger(uid)) { 31 query.uid = parseInt(uid, 10); 32 } 33 if (country !== undefined) { 34 query.country = country; 35 } 36 if (state !== undefined) { 37 query.state = state; 38 } 39 if (country_iso3 !== undefined) { 40 query.country_iso3 = country_iso3; 41 } 42 if (min_date !== undefined && max_date === undefined) { 43 query.date = {'$gte': new Date(min_date)}; 44 } 45 if (max_date !== undefined && min_date === undefined) { 46 query.date = {'$lte': new Date(max_date)}; 47 } 48 if (min_date !== undefined && max_date !== undefined) { 49 query.date = {'$gte': new Date(min_date), '$lte': new Date(max_date)}; 50 } 51 if (hide_fields !== undefined) { 52 const fields = hide_fields.split(','); 53 for (let i = 0; i < fields.length; i++) { 54 project[fields[i].trim()] = 0 55 } 56 } 57 58 console.log('Query: ' + JSON.stringify(query)); 59 console.log('Projection: ' + JSON.stringify(project)); 60 61 coll.find(query, project).sort(sort).toArray() 62 .then( docs => { 63 response.setBody(JSON.stringify(docs)); 64 response.setHeader("Contact","devrel@mongodb.com"); 65 }); 66 };
One detail to note: the payload is limited to 1MB per query. If you want to consume more data, I would recommend using the MongoDB cluster directly, as mentioned earlier, or I would filter the output to only the return the fields you really need using the hide_fields
parameter. See the documentation for more details.
#Examples
Here are a couple of example of how to run a query.
- With this one you can retrieve all the metadata which will help you populate the query parameters in your other queries:
1 curl --location --request GET 'https://webhooks.mongodb-stitch.com/api/client/v2.0/app/covid-19-qppza/service/REST-API/incoming_webhook/metadata'
- The
covid19.global_and_us
collection is probably the most complete database in this system as it combines all the data from JHU's time series into a single collection. With the following query, you can filter down what you need from this collection:
1 curl --location --request GET 'https://webhooks.mongodb-stitch.com/api/client/v2.0/app/covid-19-qppza/service/REST-API/incoming_webhook/global_and_us?country=Canada&state=Alberta&min_date=2020-04-22T00:00:00.000Z&max_date=2020-04-27T00:00:00.000Z&hide_fields=_id,%20country,%20country_code,%20country_iso2,%20country_iso3,%20loc,%20state'
Again, the REST API documentation in Postman is the place to go to review all the options that are offered to you.
#Wrap Up
I truly hope you will be able to build something amazing with this REST API. Even if it won't save the world from this COVID-19 pandemic, I hope it will be a great source of motivation and training for your next pet project.
Send me a tweet with your project, I will definitely check it out!
If you have questions, please head to our developer community website where the MongoDB engineers and the MongoDB community will help you build your next big idea with MongoDB.