Creating an Asteroid Map with a NASA API
Tags: react, javascript, graphql
Published: 2022-01-11 15:25:24

---
title: Creating an Asteroid Map with a NASA API
published: true
description: There are tons of cool APIs out there that give us access to all kinds of data. If you're into space things at all, you might be happy to know that NASA has APIs that let us work with some of their data. In this post, we'll see how you can make an asteroid map using one of these APIs.
tags: #react, #javascript, #graphql
cover_image: https://cdn.sanity.io/images/5ad74sb4/production/492e6b90f3f15d84842eb275d3e68babf2f7073c-1000x420.png?w=2000&h=2000&fit=max
canonical_url: https://mediajams.dev/post/Creating-an-Asteroid-Map-with-the-NASA-API
---

There are a lot of cool APIs out there that let you work with interesting datasets. If you are interested in space at all, then the NASA APIs might be something you want to check out.

In this post, we'll use one of the NASA APIs to create an asteroid map. This will give us a representation of how many asteroids came close to hitting the Earth and how big they were. We'll save these images to Cloudinary so we can review them later.

## Initial setup

There are a few things we need to have in place before starting on the code. First, you'll need an API key for the NASA Asteroids - NeoWs API we'll be using. You can [get a free one here](https://api.nasa.gov/). It'll send the API key to the email you enter.

Next, you'll need a Cloudinary account to store the asteroid map images that you can reference later. You can sign up for a [free account here](https://cloudinary.com/users/register/free).

We'll be working with a local Postgres database, so if you don't have that installed, you can [download it here](https://www.postgresql.org/download/).

Now that we have all of these things set up, we can start working on the app.

## Generate a new Redwood project

In a terminal, run the following command:

```bash
$ yarn create redwood-app asteroid-map
```

This will create a lot of new files and directories for you. Our focus will be in the `web` and `api` folders. The `web` folder is where we'll write all fo the front-end code in React. The `api` folder is where we'll handle the Postgres connection and the GraphQL back-end.


## Create the database schema and connection

We'll start by connecting to the database and setting up our schema. First, open the `.env` file in the root of the project. You'll see a commented-out line that defines the `DATABASE_URL`. Uncomment that line and update it to match your local connection string. That might look similar to this:

```bash
DATABASE_URL=postgres://postgres:admin@localhost:5432/asteroids
```

You won't need to create a new database manually. When we run the first migration, the `asteroids` database will be created for you.

Now we can write the schema for the database. In the `api > db` folder, open `schema.prisma`. Redwood uses [Prisma](https://prisma.io/) to handle the database operations. This file is where we use the connection string and write the schema for all of our tables.

Update the `provider` value from `sqlite` to `postgresql`. This tells Primsa we're working with a Postgres instance. You can see where the connection string is being read from the `DATABASE_URL` value we set earlier.

Next, you can delete the example model and replace it with the following:

```jsx
model Map {
  id        Int    @id @default(autoincrement())
  name      String
  startDate DateTime
  endDate   DateTime
  mapUrl    String
}
```

This model represents the data we'll store in the database. The NASA API returns asteroid information based on the dates we submit, so we're storing those to know which dates correspond to the asteroid maps.

### Running a database migration

Since we have the schema in place for the table we'll save the asteroid maps to, let's go ahead and run a database migration. In your terminal, run the following command:

```bash
$ yarn redwood prisma migrate dev
```

This will create the database (if needed) and then add the `Map` table to it.

## Make the GraphQL types and resolvers

That's all we need to do on the database side of this app. Now we can turn to the GraphQL server. Redwood's CLI has a lot of commands that do some heavy lifting for us. We're going to generate the types and resolvers for our back-end with this command:

```bash
$ yarn redwood generate sdl --crud map
```

This will generate several files for us that handle all of the CRUD functionality for our maps. The only things we need to add are the types for the data we get from the NASA API and a resolver to fetch that data.

### Adding the asteroid data types

In the `api > src > graphql` directory, open the newly generated `maps.sdl.ts` file. This already has the type definitions for the CRUD queries and mutations we might use to update the database. 

Now we'll add the type to define the data we'll get from the API, the input type to send to the API, and the query we can use to return the data. Right below the `Map` type, add this code:

```jsx
type Asteroid {
  missDistance: String
  estimatedDiameter: String
}

input AsteroidInput {
  startDate: Date!
  endDate: Date!
  viewDate: Date!
}

type Query {
  asteroids(input: AsteroidInput): [Asteroid] @requireAuth
  maps: [Map!]! @requireAuth
  map(id: Int!): Map @requireAuth
}
```

That will give us access to the query and what it needs. Let's go define the resolver to fetch this data.

### Calling the NASA API through a resolver

This is one of the cool things about GraphQL. You can call another API in a resolver and the data gets sent through the same endpoint as if it were hitting your own database.

In `api > src > services > maps`, open the `maps.js` file. This has the CRUD resolvers created from that CLI command we ran earlier. Below all of these, add the following resolver to fetch the asteroid data:

```jsx
export const asteroids = ({ input }) => {
  return fetch(`https://api.nasa.gov/neo/rest/v1/feed?start_date=${input.startDate.toISOString().split('T')[0]}&end_date=${input.endDate.toISOString().split('T')[0]}&api_key=${your_api_key_really_goes_here}`)
  .then(response => {
    return response.json()
  })
  .then(rawData => {
    const data = rawData.near_earth_objects[input.viewDate.toISOString().split('T')[0]]

    const asteroids = data.map(value => {
      return {
        missDistance: value.close_approach_data[0].miss_distance.kilometers,
        estimatedDiameter: value.estimated_diameter.kilometers.estimated_diameter_max
      }
    })
    
    return asteroids
  })
}
```

This resolver takes the input we pass to it and makes this request to the API. Like with many API requests, we have to send the inputs in a particular format. That's why we're splitting the date string the way we are. GraphQL passes the date in a format the NASA API doesn't like.

Then we get the data from the response and check out the asteroids that were close by on the `viewDate` we pass in. This date can be any time between the start and end dates. We take the data returned from the API and extract the values we need and that's what we pass in a successful response.

That's everything for the back-end! We have all of the types and resolvers we need to get the asteroid data and save things to the database. We can turn our attention to the front-end where we'll wrap things up.

## Building the user interface

Let's jump right in. There's one package that we need to install in order to save the asteroid maps we create. In your terminal, go to the `web` directory and run:

```bash
$ yarn add html-to-image
```

This will allow us to capture an image of the asteroid map really quickly.

We can use the Redwood CLI to generate the asteroid map page for us. So in your terminal go back to the root of the project and run the following command:

```bash
$ yarn redwood generate page asteroid
```

This will update the `Routes.tsx` file to have this new path and it generates a few files for us in `web > src > pages > AsteroidPage`. The file we will work in is `AsteroidPage.tsx`. Open this file and delete all of the existing import statements and replace them with these:

```jsx
import { useQuery, useMutation } from '@redwoodjs/web'
import { useState, useRef } from 'react'
import { toPng } from 'html-to-image'
```

After these imports, we can add the GraphQL query to get our asteroid data and the mutation to save the map to the Cloudinary and the database.

```jsx
const CREATE_MAP_MUTATION = gql`
  mutation CreateMapMutation($input: CreateMapInput!) {
    createMap(input: $input) {
      id
    }
  }
`

const GET_ASTEROIDS = gql`
  query GetAsteroids($input: AsteroidInput!) {
    asteroids(input: $input) {
      missDistance
      estimatedDiameter
    }
  }
`
```

### Adding states and using hooks in the component

With all of the imports and GraphQL definitions in place, let's start working inside the `AsteroidPage` component. You can delete everything out of the component because we'll be writing a lot of different code.

We'll start by adding the states and other hooks we need for the component.

```jsx
const [createMap] = useMutation(CREATE_MAP_MUTATION)

const canvasRef = useRef(null)

const [startDate, setStartDate] = useState("2021-08-12")
const [endDate, setEndDate] = useState("2021-08-15")
const [viewDate, setViewDate] = useState("2021-08-13")

const { loading, data } = useQuery(GET_ASTEROIDS, {
  variables: { input: { startDate: startDate, endDate: endDate, viewDate: viewDate }},
})
```

First, we create the method that does the mutation to add new records to the database. Then we set the canvas ref that will hold the image of the asteroid map. Next, we set a few different date states. These will let us adjust what's in the map we save and what we see in the app.

Then there's the data fetch query. This calls that resolver we made to get the asteroid data from the NASA API. We pass in the `input` in the shape we defined in the types on the back-end. These values come from the states, so whenever the state values change we can get a new asteroid map.

### Having a loading state

You'll notice that we have a `loading` value from the `useQuery` call. This tells us if the data is still being fetched. It's important to have some kind of element that tells the user a page is loading. This also prevents the app from crashing when the data isn't available yet. So below the data query, add this code:

```jsx
if (loading) {
  return <div>Loading...</div>
}
```

This just renders a loading message on the page.

### The elements that get rendered

Now that we have the data coming in, let's write the return statement for what should render on the page. Below the loading state check, add the following code and we'll go through it:

```jsx
return (
  <>
    <h1>AsteroidPage</h1>
    <form onSubmit={submit}>
      <div>
        <label htmlFor="mapName">Map Name</label>
        <input type="text" name="mapName" />
      </div>
      <div>
        <label htmlFor="startDate">Start Date</label>
        <input type="date" name="startDate" />
      </div>
      <div>
        <label htmlFor="endDate">End Date</label>
        <input type="date" name="endDate" />
      </div>
      <div>
        <label htmlFor="viewDate">View Date</label>
        <input type="date" name="viewDate" />
      </div>
      <button type="submit">Save Asteroid Map</button>
    </form>
    <button type="button" onClick={makeAsteroidMap}>View Map</button>
    <canvas id="asteroidMap" ref={canvasRef} height="3000" width="3000"></canvas>
  </>
)
```

There's not as much going on as it might seem. We have a form that has a few input elements for the name we want to give an asteroid map and the dates we need to get the data and image. This form has a submit button that fetches new asteroid data based on our inputs and saves a new map.

There's another button that lets us view the asteroid map in the canvas element below it. The canvas element is what we target in the `useRef` hook above. The form and view map buttons have functions that we need to write.

If you want to look at the app so far, run `yarn redwood dev` in your terminal. You should see something like this.

![basic user form for asteroid data input](https://res.cloudinary.com/jesse-thisdot/image/upload/v1639069205/e-603fc55d218a650069f5228b/ifpzozvsupyh1ovia8uz.png)

### The submit function

We'll add this function right below the loading state check. This will get the form data, update the date states, take a snapshot of the asteroid map in the canvas, upload it to Cloudinary, and then make a new database record.

```jsx
async function submit(e) {
  e.preventDefault()
  const mapName = e.currentTarget.mapName.value
  const startDate = e.currentTarget.startDate.value
  const endDate = e.currentTarget.endDate.value
  const viewDate = e.currentTarget.viewDate.value

  setStartDate(startDate)
  setEndDate(endDate)
  setViewDate(viewDate)
  
  if (canvasRef.current === null) {
    return
  }

  const dataUrl = await toPng(canvasRef.current, { cacheBust: true })

  const uploadApi = `https://api.cloudinary.com/v1_1/${cloudName}/image/upload`

  const formData = new FormData()
  formData.append('file', dataUrl)
  formData.append('upload_preset', upload_preset_value)

  const cloudinaryRes = await fetch(uploadApi, {
    method: 'POST',
    body: formData,
  })

  const input = {
    name: mapName,
    startDate: new Date(startDate),
    endDate: new Date(endDate),
    mapUrl: cloudinaryRes.url
  }

  createMap({
    variables: { input },
  })
}
```

You'll need to get your `cloudName` and upload preset value from your Cloudinary console. The only function left to write is the one to draw the asteroid map on the canvas.

### Drawing the asteroid map

This will create a different sized circle at various distances from the left side of the page to show how close they were to Earth and how big they were.

```jsx
function makeAsteroidMap() {
  if (canvasRef.current.getContext) {
    let ctx = canvasRef.current.getContext('2d')

    data.asteroids.forEach((asteroid) => {
      const scaledDistance = asteroid.missDistance / 75000
      const scaledSize = asteroid.estimatedDiameter * 100
      let circle = new Path2D()

      circle.arc(scaledDistance * 2, scaledDistance, scaledSize, 0, 2 * Math.PI)
  
      ctx.fill(circle)
    })
  }
}
```

The scaling here isn't based on anything in particular, so feel free to play around with the math!

Now if you run the app and click the View Map button, you'll see something like this.

![the asteroid map](https://res.cloudinary.com/jesse-thisdot/image/upload/v1639072537/e-603fc55d218a650069f5228b/uchtdfxvh86f6qw4i23r.png)

If you update the dates, you can view a different map and save it to the database. That's all of the code for this app!

Now you can see how close we almost came to an asteroid event every day.

## Finished code

You can take a look at the complete project in the [`asteroid-map` folder of this repo](https://github.com/flippedcoder/blog-examples/tree/main/asteroid-map). Or you can take a look at the front-end in [this Code Sandbox](https://codesandbox.io/s/reverent-lederberg-kbxsr). You'll have to update some values to match yours in order for this to work.

## Conclusion

Working with external APIs is something we commonly do and GraphQL is one of the ways we can centralize all of the APIs we call. Using this as a tool to make visual representations of how close we came to being hit by asteroids every day is just a fun way to use that functionality.