Launch Info (live on launchinfo.org) is a project I built to be able to keep track of upcoming rocket launches. Growing tired of the hard-to-use, old-fashioned, and feature-lacking alternatives, I decided to build my own. Some of the features I wanted were:
- A variety of launches, not just those from the largest and most well-known providers
- The ability to find out more about a launch provider or rocket without having to go to Wikipedia
- The ability to filter launches from a specific launch provider or rocket
- T- countdowns to launches
The project has a simple architecture, since it relies on an external API, Launch Library 2. It is hosted on AWS using CloudFront and deployed using a custom CICD pipeline I wrote in CloudFormation. You can find the CICD pipeline here.
The project is a Single Page Application built using Vue.js and the Vue Router.
During the development of the project I faced a few challenges. Some notable were:
- Different response structure and content. Depending on what data was available for different launches, rockets, and agencies, the structure of the API responses could vary greatly. To avoid "cannot read property of null" errors, I wrote a recursive function for reading properties "safely" (first code highlight below)
- Sending data between components. As the project grew with more and more components and subcomponents, I ended up using a lot of props. This both decreased productivity and introduced some unnecessary errors
- Maintaining an intuitive file structure as the project grew. Due to a lack of planning at the start of the project, I had to restructure the codebase multiple times. Some examples are the commits 6be774a, 5db0511, and 51015c6
As with any project, there are some parts of the codebase that I'm more proud of than others. Two examples of my top code in this project follow below:
// Recursive function for reading a paramater following a certain path
function readProperty(path, parent) {
// Get the topmost property name
const property = path.shift();
// Check if the property exists
if (parent[property]) {
// If there are no more properties after this, return the value of the property
if (path.length == 0) {
return parent[property];
} else {
// Else keep looking deeper
return readProperty(path, parent[property]);
}
} else {
// Return undefined if the property does not exist
return undefined;
}
}
// Function that handles interaction with the API
export default function callApi(path, callback, errorCallback) {
// Query the API using the requested path
fetch(`https://ll.thespacedevs.com/2.0.0/${path}`)
.then(response => {
// Check if the response was successful
if (response.status == 200) {
// If it was, parse the data to JSON
const data = response.json();
return data;
} else {
// If the response failed, call the error callback sent by the view
if (errorCallback) {
errorCallback(response.status);
}
return null;
}
})
.then(data => {
// Call the callback if the response was successful
if (data != null) {
callback(data);
}
})
.catch(error => {
console.log("Error retrieving data from the API:", error);
errorCallback(error.status);
});
}
I learned a ton during this project. Some of the key takeaways I will keep in mind for future projects are:
- Plan thoroughly, and decide on a scalable file structure early on
- Use a centralized store/state management pattern (Vuex) to avoid endless props
- Use unit testing to prevent bugs