Why
Scripts typically require some level of tinkering and tweaking to get the desired output. If written in Javascript, developers have to remember the shape of input and output. I personally have to log output to recall what I'm getting as a response. By now, most of the Javascript community is aware of the perks of switching to Typescript. If you'd like a refresher, visit Serokell's post
What are we making?
We are going to take input from the covid tracking api and format it in a slightly different way to prepare to graph it on a chart. Here is an example of a day's US output
Sample Input
[
{
"date":20200916,
"states":56,
"positive":6597783,
"negative":81976741,
"pending":10587,
"hospitalizedCurrently":30278,
"hospitalizedCumulative":390624,
"inIcuCurrently":6308,
"inIcuCumulative":18961,
"onVentilatorCurrently":1651,
"onVentilatorCumulative":2090,
"recovered":2525573,
"dateChecked":"2020-09-16T00:00:00Z",
"death":188802,
"hospitalized":390624,
"lastModified":"2020-09-16T00:00:00Z",
"total":88585111,
"totalTestResults":88574524,
"posNeg":88574524,
"deathIncrease":1202,
"hospitalizedIncrease":1517,
"negativeIncrease":625601,
"positiveIncrease":40021,
"totalTestResultsIncrease":665622,
"hash":"e66c44b8b93e51c84321a2933d4031d75084a04c"
},
...
]
Sample Output
[
{
"x":09-16-2020,
"y":{
"positive":6597783,
"negative":81976741,
"pending":10587,
"hospitalizedCurrently":30278,
"hospitalizedCumulative":390624,
"inIcuCurrently":6308,
"inIcuCumulative":18961,
"onVentilatorCurrently":1651,
"onVentilatorCumulative":2090,
"recovered":2525573,
"death":188802,
"hospitalized":390624,
"total":88585111,
"totalTestResults":88574524,
"posNeg":88574524,
"deathIncrease":1202,
"hospitalizedIncrease":1517,
"negativeIncrease":625601,
"positiveIncrease":40021,
"totalTestResultsIncrease":665622,
},
...
]
Instead of starting with a fully functional Javascript script... we'll get a script running and switch to Typescript to start building out the structure and documenting for future changes
Getting Started with a Javascript example
Setup a project space
mkdir script-in-ts && cd script-in-ts
Then initialize a package.json
that will allow you to specify scripts and dependencies needed
npm init --y
We're going to get the script running in Javascript first so lets install the necessary dependencies
npm install @babel/core @babel/node @babel/preset-env
babel allows us the ability compile modern javascript. Both @babel/core
and @babel/node
make that possible while @babel/preset-env allows us to specify things such as the node version or browser support
Setting up babel to run Javascript
Add a .babelrc
file
touch .babelrc && code .babelrc
Paste in the following setup to specify use of node version 10. This will give us access to things like the spread operator which you will see here soon
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "10"
}
}
]
]
}
We're ready to create!
Make an index.js
file
touch index.js && code index.js
Get started with a hello world example
console.log("hello world")
Open package.json
and add a start script like so
{
...
"scripts": {
"start": "babel-node index.js",
...
},
...
}
Let's make sure our script runs and everything is setup to move onto Typescript
npm run start
hello world // expected output
Migrating Javascript to Typescript
Install dependencies
npm install typescript @babel/preset-typescript @babel/plugin-transform-typescript
The first dependency is Typescript itself and the second is the preset to transpile Typescript using babel
We'll need to update our .babelrc
to include the Typescript preset like so
{
"presets": [
"@babel/preset-typescript",
[
...
]
]
}
Create a tsconfig.json
file
npx tsc --init
Rename index.js
to index.ts
mv index.js index.ts
Update your start script in package.json
{
...
"scripts": {
"start": "babel-node index.ts --extensions '.ts'",
...
},
...
}
Although we've added @babel/preset-typescript babel still needs a specification to allow .ts
files
Validate babel compiles and runs index.ts
npm run start
hello world // expected output
🤞 Although this doesn't seem like a big step... it is. Unless you've configured babel frequently you forget these setup instructions and could put your search abilities to work
Getting your hands dirty with Typescript 👨🏻💻
If you're unfamiliar with defining types and interfaces I would highly encourage you to take a break here and familiarize yourself with the subtle differences between Javascript and Typescript. I enjoy this devhints cheatsheet when I was getting familiar.
Fetching data
We're going to type out the response from the United States COVID-19 impact in json format.
Feel free to use whatever fetching library you prefer. I'll be using node-fetch
npm install node-fetch @types/node-fetch
Fetch and log the response
import fetch from "node-fetch"
;(async () => {
const response = await fetch(
"https://api.covidtracking.com/v1/us/daily.json"
)
const json = await response.json() //
console.log(json)
})()
Typing the input
Your console should be logging something similar to this...
[
{
"date":20200916,
"states":56,
"positive":6597783,
"negative":81976741,
"pending":10587,
"hospitalizedCurrently":30278,
"hospitalizedCumulative":390624,
"inIcuCurrently":6308,
"inIcuCumulative":18961,
"onVentilatorCurrently":1651,
"onVentilatorCumulative":2090,
"recovered":2525573,
"dateChecked":"2020-09-16T00:00:00Z",
"death":188802,
"hospitalized":390624,
"lastModified":"2020-09-16T00:00:00Z",
"total":88585111,
"totalTestResults":88574524,
"posNeg":88574524,
"deathIncrease":1202,
"hospitalizedIncrease":1517,
"negativeIncrease":625601,
"positiveIncrease":40021,
"totalTestResultsIncrease":665622,
"hash":"e66c44b8b93e51c84321a2933d4031d75084a04c"
},
...
]
Lets make an interface to replicate it!
import fetch from "node-fetch"
interface USInputDay {
date: Date
states: number
positive: number
negative: number
pending: number
hospitalizedCurrently: number
hospitalizedCumulative: number
inIcuCurrently: number
inIcuCumulative: number
onVentilatorCurrently: number
onVentilatorCumulative: number
recovered: number
dateChecked: Date
death: number
hospitalized: number
lastModified: Date
total: number
totalTestResults: number
posNeg: number
deathIncrease: number
hospitalizedIncrease: number
negativeIncrease: number
positiveIncrease: number
totalTestResultsIncrease: number
hash: string
}
;(async () => {
const response = await fetch(
"https://api.covidtracking.com/v1/us/daily.json"
)
const json = await response.json() //
console.log(json)
})()
The interface above is an array of USInputDay
so if we apply that type to the json response constant
import fetch from "node-fetch"
interface USInputDay {
date: Date
states: number
positive: number
negative: number
pending: number
hospitalizedCurrently: number
hospitalizedCumulative: number
inIcuCurrently: number
inIcuCumulative: number
onVentilatorCurrently: number
onVentilatorCumulative: number
recovered: number
dateChecked: Date
death: number
hospitalized: number
lastModified: Date
total: number
totalTestResults: number
posNeg: number
deathIncrease: number
hospitalizedIncrease: number
negativeIncrease: number
positiveIncrease: number
totalTestResultsIncrease: number
hash: string
}
;(async () => {
const response = await fetch(
"https://api.covidtracking.com/v1/us/daily.json"
)
const json: USInputDay[] = await response.json()
console.log(json)
})()
We can now get a taste of the perks to switching to Typescript!
Auto-completion makes future requests to change input or output easy. We no longer need to log the file fetch to understand what it should look like!
Typing the output
In comparison to the input format we are just going to separate this into x
and y
values to show how to manipulate this into a new format
import fetch from "node-fetch"
interface USInputDay {
date: Date
states: number
positive: number
negative: number
pending: number
hospitalizedCurrently: number
hospitalizedCumulative: number
inIcuCurrently: number
inIcuCumulative: number
onVentilatorCurrently: number
onVentilatorCumulative: number
recovered: number
dateChecked: Date
death: number
hospitalized: number
lastModified: Date
total: number
totalTestResults: number
posNeg: number
deathIncrease: number
hospitalizedIncrease: number
negativeIncrease: number
positiveIncrease: number
totalTestResultsIncrease: number
hash: string
}
interface USOutputDay {
x: Date
y: Omit<
USInputDay,
"date" | "dateChecked" | "lastModified" | "hash"
>
}
;(async () => {
const response = await fetch(
"https://api.covidtracking.com/v1/us/daily.json"
)
const json: USInputDay[] = await response.json()
})()
Above we made reuse of the USInputDay
interface and we used the Omit utility to delete the keys we don't want to account for
Format Output
Now all we have to do is format the input into the output structure
import fetch from "node-fetch"
interface USInputDay {
date: Date
states: number
positive: number
negative: number
pending: number
hospitalizedCurrently: number
hospitalizedCumulative: number
inIcuCurrently: number
inIcuCumulative: number
onVentilatorCurrently: number
onVentilatorCumulative: number
recovered: number
dateChecked: Date
death: number
hospitalized: number
lastModified: Date
total: number
totalTestResults: number
posNeg: number
deathIncrease: number
hospitalizedIncrease: number
negativeIncrease: number
positiveIncrease: number
totalTestResultsIncrease: number
hash: string
}
interface USOutputDay {
x: Date
y: Omit<
USInputDay,
"date" | "dateChecked" | "lastModified" | "hash"
>
}
;(async () => {
const response = await fetch(
"https://api.covidtracking.com/v1/us/daily.json"
)
const json: USInputDay[] = await response.json()
const output: USOutputDay[] = json.map(
({ date, dateChecked, lastModified, hash, ...theRest }) => ({
x: date,
y: theRest
})
)
})()
I got a little fancy here and used the spread operator. Since I knew the output format only excluded a few keys from the input I pulled the keys I wanted and the ...theRest
is all the remaining keys in the object I need to satisfy my output.
Write it to file
Last step... I promise 😉
Import the file system and write it to an output file
import fetch from "node-fetch"
import { writeFileSync } from "fs"
interface USInputDay {
date: Date
states: number
positive: number
negative: number
pending: number
hospitalizedCurrently: number
hospitalizedCumulative: number
inIcuCurrently: number
inIcuCumulative: number
onVentilatorCurrently: number
onVentilatorCumulative: number
recovered: number
dateChecked: Date
death: number
hospitalized: number
lastModified: Date
total: number
totalTestResults: number
posNeg: number
deathIncrease: number
hospitalizedIncrease: number
negativeIncrease: number
positiveIncrease: number
totalTestResultsIncrease: number
hash: string
}
interface USOutputDay {
x: Date
y: Omit<
USInputDay,
"date" | "dateChecked" | "lastModified" | "hash"
>
}
;(async () => {
const response = await fetch(
"https://api.covidtracking.com/v1/us/daily.json"
)
const json: USInputDay[] = await response.json()
const output: USOutputDay[] = json.map(
({ date, dateChecked, lastModified, hash, ...theRest }) => ({
x: date,
y: theRest
})
)
writeFileSync("formatted.json", JSON.stringify(output))
})()
That's it! Now your script is ready to tweak for a new change or to use as is!
If you got lost at any point, no fear, here is a repository showing what was made!
Summary
We learned how to setup a project from scratch to use babel and run basic Javascript files. We then converted Javascript to Typescript and setup babel to handle Typescript files. Next we learned how to fetch files using node-fetch
. We gained some experiences building types/interfaces to control input and output for benefits such as auto-completion. Lastly, we learned to write content to a file using fs
.
Top comments (1)
Really enjoyed the reading!, straight to the point and very well explained!..