loading...

Scripting from Javascript to Typescript

keonik profile image John Fay ・7 min read

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"
 },
 ...
]
Enter fullscreen mode Exit fullscreen mode

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,
 },
 ...
]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then initialize a package.json that will allow you to specify scripts and dependencies needed

npm init --y
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
        }
      }
    ]
  ]
}
Enter fullscreen mode Exit fullscreen mode

We're ready to create!

Make an index.js file

touch index.js && code index.js
Enter fullscreen mode Exit fullscreen mode

Get started with a hello world example

console.log("hello world")
Enter fullscreen mode Exit fullscreen mode

Open package.json and add a start script like so

{
...
   "scripts": {
       "start": "babel-node index.js",
      ...
   },
...
}
Enter fullscreen mode Exit fullscreen mode

Let's make sure our script runs and everything is setup to move onto Typescript

npm run start
  hello world // expected output
Enter fullscreen mode Exit fullscreen mode

Nailed it 🎉 Onto part 2

Migrating Javascript to Typescript

Install dependencies

npm install typescript @babel/preset-typescript @babel/plugin-transform-typescript
Enter fullscreen mode Exit fullscreen mode

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",
    [
     ...
    ]
  ]
}
Enter fullscreen mode Exit fullscreen mode

Create a tsconfig.json file

npx tsc --init
Enter fullscreen mode Exit fullscreen mode

Rename index.js to index.ts

mv index.js index.ts
Enter fullscreen mode Exit fullscreen mode

Update your start script in package.json

{
...
"scripts": {
 "start": "babel-node index.ts --extensions '.ts'",
 ...
},
...
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Not impressed

🤞 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
Enter fullscreen mode Exit fullscreen mode

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)
})()
Enter fullscreen mode Exit fullscreen mode

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"
 },
 ...
]
Enter fullscreen mode Exit fullscreen mode

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)
})()
Enter fullscreen mode Exit fullscreen mode

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)
})()
Enter fullscreen mode Exit fullscreen mode

We can now get a taste of the perks to switching to Typescript!

typescript auto-completion example

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()
})()
Enter fullscreen mode Exit fullscreen mode

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
    })
  )
})()
Enter fullscreen mode Exit fullscreen mode

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.

Slick huh!?

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)) 
})()
Enter fullscreen mode Exit fullscreen mode

That's it! Now your script is ready to tweak for a new change or to use as is!

Celebration Time

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.

Discussion

pic
Editor guide
Collapse
crisarji profile image
crisarji

Really enjoyed the reading!, straight to the point and very well explained!..