I just love seeing the progression of style over the decades especially with such an iconic character. I decided to try to figure out a way to automate this and display it to the user. Now, before I go any further, let me state that I’m not going to run this demo “live”. Why? First - I’m still not 100% sure how to stay “safe” in the free tier with Azure Functions. Last month I got a bill for 40 dollars because I made a wrong selection in a project and while that’s my fault, I still feel a bit burned by it. Secondly, Marvel themselves have a limit on their API usage. It’s a fair limit of course, but it’s also something I just don’t want to worry about. If either Microsoft or Marvel want to help me out here, just drop me a line! I won’t hold my breath. ;) That being said, all of the code I’m about to show can be found at my GitHub repo here: https://github.com/cfjedimaster/marvelcharacterovertime

The Back End

My back end is built using Azure Functions. This was the first time I made use of Visual Studio Code integration and damn did it work well. I think it took maybe twenty minutes of setup or so but once done, it was one quick command to deploy to Azure when I had updates. It was also easy to run the code locally. From my limited experience so far, this is the best way to work with Azure Functions (obviously if you are a Code user) and it’s what I plan on using in the future.

My application required only two specific features - the ability to search for characters and then the ability to find related covers over time. Let’s start with the character search endpoint:

constrp=require('request-promise');constAPI_PUB_KEY=process.env.API_PUB_KEY;constAPI_PRI_KEY=process.env.API_PRI_KEY;constcrypto=require('crypto');module.exports=asyncfunction(context,req){if(req.query.name){letname=req.query.name;letbaseUrl=`https://gateway.marvel.com:443/v1/public/characters?nameStartsWith=${encodeURIComponent(name)}&apikey=${API_PUB_KEY}`;letts=newDate().getTime();lethash=crypto.createHash('md5').update(ts+API_PRI_KEY+API_PUB_KEY).digest('hex');baseUrl+="&ts="+ts+"&hash="+hash;//console.log('baseUrl', baseUrl);returnrp({url:baseUrl,json:true}).then(res=>{//console.log(res.data);letresults=[];if(res.data.total>0){results=res.data.results.map(r=>{return{id:r.id,name:r.name,thumbnail:r.thumbnail.path+'.'+r.thumbnail.extension};});}context.res={// status: 200, /* Defaults to 200 */body:results,headers:{'Content-Type':'application/json','Access-Control-Allow-Origin':'*'}};});}else{context.res={status:400,body:"Please pass a name on the query string"};}};

This one is the easiest as all it needs to do is use the characters endpoint with the nameStartsWith argument. This will let you enter a value, like ‘spider’, and get results. The stuff with the time and hash is simply part of Marvel’s API security which frankly feels like overkill but there it is. I get the results and then map it down a bit to remove a lot of data I don’t need. This makes the communication between Azure Functions and my web app a lot zippier as I’m not returning unnecessary data.

Cool! So far so good, I’m sure the next endpoint will be just as easy, right? Hah!

Marvel doesn’t have an API that returns covers with certain characters, but you can search comics for a character and I figured that would be close enough. In order to get my data, I thought I’d search for a year’s worth of data for results including a character. Unfortunately, the character API doesn’t return when a character was first seen. So in order to estimate that, I did a search with a date range from 1950 to 2090. Please feel free to come find me in 2090 and complain.

I sort those results by the sale date and then use the first result as indicative of when the character’s first appearance was. I didn’t test this heavily but it seemed to work well with Spider-Man.

Once you have that - you can then ask for comics from every year from the initial year till the current year. And that’s basically it. Here’s the code:

constrp=require('request-promise');constAPI_PUB_KEY=process.env.API_PUB_KEY;constAPI_PRI_KEY=process.env.API_PRI_KEY;constcrypto=require('crypto');module.exports=asyncfunction(context,req){/*
First idea:
first we do a comic search with a date range of 1950-2090 in an attempt to find the first comic
this gives us X. We then get 10 comics from X to THIS_YEAR
Second idea:
go from THIS_YEAR to THIS_YEAR-- until we get nothing back.
however, it's possible for a character to 'go away' for a few years. so maybe we would allow for '3 strikes'
of no results and only stop when we've hit that limit
*/if(req.query.id){letid=req.query.id;returnnewPromise((resolve,reject)=>{// ok - try to get first issueletbaseUrl=`https://gateway.marvel.com:443/v1/public/comics?dateRange=1950-01-01%2C2090-01-01&characters=${id}&orderBy=onsaleDate&apikey=${API_PUB_KEY}`;letts=newDate().getTime();lethash=crypto.createHash('md5').update(ts+API_PRI_KEY+API_PUB_KEY).digest('hex');baseUrl+="&ts="+ts+"&hash="+hash;//console.log('baseUrl', baseUrl);rp({url:baseUrl,json:true}).then(res=>{letfirstDate='';if(res.data&&res.data.results&&res.data.results.length>0){letfirstResult=res.data.results[0];// from what I know the type is always onsaleDatefirstDate=newDate(firstResult.dates[0].date).getFullYear();}// no firstDate?if(firstDate===''){context.res={body:{result:[]},headers:{'Content-Type':'application/json'}};resolve();//not sure I need thisreturn;}//temp hack://firstDate = 2015;//get this yearletthisYear=newDate().getFullYear();console.log('going to go from '+firstDate+' to '+thisYear);letcoverCalls=[];for(letx=firstDate;x<=thisYear;x++){letdateStr=x+'-01-01%2C'+x+'-12-31';letthisUrl=`https://gateway.marvel.com:443/v1/public/comics?dateRange=${dateStr}&characters=${id}&orderBy=onsaleDate&limit=10&apikey=${API_PUB_KEY}`;letts=newDate().getTime();lethash=crypto.createHash('md5').update(ts+API_PRI_KEY+API_PUB_KEY).digest('hex');thisUrl+="&ts="+ts+"&hash="+hash;console.log(thisUrl);coverCalls.push(rp({url:thisUrl,json:true}));}Promise.all(coverCalls).then((data)=>{console.log('in the all for calling covers');letresults=[];//each index of data is year X, we will return the: year, [title, cover]for(varx=0;x<data.length;x++){letitem={};item.year=x+firstDate;item.comics=[];for(vary=0;y<data[x].data.results.length;y++){letcomic={};comic.title=data[x].data.results[y].title;comic.cover=data[x].data.results[y].thumbnail.path+'.'+data[x].data.results[y].thumbnail.extension;item.comics.push(comic);}results.push(item);}context.res={body:{result:results},headers:{'Content-Type':'application/json','Access-Control-Allow-Origin':'*'}};resolve();}).catch(e=>{console.log('error',e);});});});}else{context.res={status:400,body:"Please pass an id (for the character) on the query string"};}};

You’ll notice I’m using an array of Promises so I can quickly fire off a bunch of requests at once and wait for them all to complete. Marvel doesn’t have a “throttle” limit so this may not always work well for other APIs. Finally, note I’m once again mapping the results back to limit the data being sent to the front end.

The Front End

The front end was a simple affair - prompt for the character, show results, then render comic covers over time. I built it using Vue.js and had quite a bit of design help from my buddy Garth. I really wish I could run this live for yall but as I said above, I just can’t do it that for free and within the API limits.

Let’s start with the character search result screen:

After you select a character, I then hit the back end, which frankly worked really freaking fast, especially considering how much data someone like Spider-Man has. Here are four screen shots from a heck of a long set of results:

There really isn’t much more here than some Ajax calls. There’s definitely more I could do (as the comments themselves say) but it gets the job done.

If you want to see the full set of results, I can say that the “print to pdf” version is 150 pages. That’s partially because there’s some oddities in PDF form that make it take up more vertical space, but it’s truly kind of impressive to look at almost sixty years of Spider-Man over time.

So I felt bad and did a quick hack. I used devtools to copy all the image URLs, I then whipped up a quick CodePen that just rendered them all - nearly 500 of them. You can view it here: https://codepen.io/cfjedimaster/full/QJwyOB/

About Raymond Camden

Raymond is a developer advocate looking for his next gig. He focuses on JavaScript, serverless and enterprise cat demos.
If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support.