EventGet 50% off your ticket to MongoDB.local NYC on May 2. Use code Web50!Learn more >>
MongoDB Developer
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right

Build an App With Python, Flask, and MongoDB to Track UFOs

Shelby Carpenter15 min read • Published Feb 06, 2023 • Updated Feb 06, 2023
Facebook Icontwitter iconlinkedin icon
Rate this tutorial

Do You Want to Believe?

Have you ever looked up at the night sky and wondered what’s out there? Even as our knowledge of the cosmos expands — especially with the launch of the James Webb Space Telescope and the spectacular stream of photos it sends back — so many mysteries remain. A perpetual and existential question is whether there is other intelligent life out there.
While this question remains unanswered, there are plenty of citizen documentarians out there tracking appearances of unidentified flying objects (UFOs). The National UFO Reporting Center (NUFORC) collects reports of UFO sightings and stores them in a publicly accessible data bank.
To explore the capabilities of MongoDB and the Pymongo driver used with Python and Flask, I built a full-stack web application that allows users to look up reports of UFO sightings based on NUFORC data as well as submit a report back to a central database in the cloud with MongoDB Atlas.
You can access the full codebase for the app, but follow along for the rest of the tutorial to learn how to build this on your own!

Application requirements

To be successful with this tutorial, you should already have the following ready to go:

Upload data and organize MongoDB collection

To get started, you’ll need to have the data from NUFORC uploaded to MongoDB Atlas. To do this, download a free scrubbed CSV file of this data available on Kaggle.
Log into Atlas and copy your connection string. Then, open up MongoDB Compass on your computer and click the “New Connection” button to enter your Atlas connection string and connect to your Atlas database. From there, create a database called “ufos” and a collection in that database, also called “ufos”. When you click on the “ufos” collection, you’ll be taken to a screen where you will upload the CSV file of UFO reports you downloaded from Kaggle.
A view of the documents tab in MongoDB Compass
Now you have a collection of UFO reports in the cloud with Atlas that you can use as part of your application.

Set up Flask

Regardless of your chosen IDE and whether you’re working in a virtual environment or on your local machine, follow Flask best practices for organizing your workspace for building this app.
I’ve been exploring GitHub Codespaces lately and started building off of a sample Flask application there. But you may have the best experience using VS Code and the MongoDB VS Code Extension.
Create one folder for everything for your application called “ufo-spotter-app”. Within that folder, create one folder called static which will eventually contain the assets used by your templates. Create a second folder called templates within the ufo-spotter-app parent folder where your .html template pages will live.

Build your foundational templates and styling

Now that everything is set up in your development environment and you have your data uploaded to MongoDB, you’re ready to start coding! You’ll start by building out your templates. In Flask, templates contain static data as well as dynamic data that can be passed in with the help of the Jinja library in Flask. You can learn more about Flask templates in the Flask documentation.

Create layout.html

The first template you’ll build is layout.html. This template will serve as the basis of the other templates, with Jinja making it easier to build off of layout.html without having to write the same code again and again. See below for the code for layout.html, which establishes the document type as html, the language as English, the default CSS styling coming from Bootstrap but with a link to a second main.css file we’ll create next, and the navbar your users will rely on to navigate the website.
The html in this section creates the navbar with clickable sections at the upper left that will be consistent across all pages:
UFO spotter

Create Main.css

Now, let’s add a little flair to the default CSS styling we already have from Bootstrap. Create a file called Main.css in the “static” folder with the following code:
The font choice of American Typewriter is intended to give off a minimalist and spooky vibe in line with the UFO theme, while still being easy to read.

Let users look up UFO sightings

Now we’re really getting into the meat of the application. The next thing we’re going to do is set up our web application to allow users to enter a U.S. city and state and get back statistics on UFO sightings as well as a list of the 10 most recent sightings. This way, they’ll know what to be on the lookout for.

Create city.html

Let’s make the html template that we’ll render to let users input a U.S. city and state. First you have to enter {% extends "layout.html" %} at the top to base this file off of layout.html. Then we’ve got a fun image (sourced from Flaticon), some text to tell website visitors how to use the page, and a form. The first section of the form lets a user enter text for the name of the city. The next uses a Jinja for loop to provide a select box for U.S. states based on a Python list of state abbreviations in the app.py file. After that, we just have the “Submit” button to trigger the form completion and a footer at the bottom to provide appropriate attribution for the UFO image.
Here’s what the page looks like when rendered:
The landing page on the UFO Spotter website where the website visitor can enter a U.S. city and state and look up reports of UFO sightings

Look up data with the MongoDB Query API in helpers.py

One of the great features of MongoDB is the document model, which gives you the flexibility to model your data as JSON-like documents that easily map to objects in your code. I’ve personally found this much easier than a relational approach using a myriad of different tables and managing table joins for different projects.
To support the main app.py file for this application, we will create a helpers.py file with two helper functions that pull data from the MongoDB Atlas database using the MongoDB Query API.
The first function, get_count(), uses the $match and $count aggregation stages in the Query API to find UFO reports with the same U.S. city and state as input by the user and returns the total number of UFO reports matching that city/state combination.
The second function, get_ufos(), uses the $match and $sort aggregation stages. This function also finds UFO reports with the same U.S. city and state as input by the user, and then returns all of those reports sorted by the date of the sighting from most recent to oldest.

Create app.py

Now let’s take a look at the first portion of the app.py file. First you need to import re to allow for regular expressions. You’ll also need to import date from datetime to help with processing the dates related to sightings. You’ll get Flask for building your Flask app as well as render_template to help with displaying templates and request for taking user inputs from forms in your html templates. Finally, you’ll need to import the functions we built in helpers.py.
After a few more lines of code to set up your application, you’ll set up your client variable to connect to MongoDB Atlas with your connection string (make sure to substitute in your actual connection string for the placeholder in the code). And you’ll want to declare an array of U.S. state abbreviations to be displayed in city.html.
Now that you’ve set this Python file as the core file at the heart of your Flask application, you’ll create your city() function with a decorator that assigns the URL and allows this function to receive both GET and POST HTTP requests.
When receiving a GET request from the user, this function will display the city.html file we created earlier, passing in the states array for us in that file.
If the function is called with an HTTP POST request, we process the U.S. city and state input by the user once they hit the “Submit” button in city.html. This function also includes some error-handling and returns an appropriate error message to the user if the city name they submit includes a numeral or a special character, which it never should.
Using the .lower() method in Python, we convert the uppercase abbreviation for the U.S. state to lowercase to match the format of the UFO reports data set.
We are able to get the number of UFO reports in the state specified by the user with the .count_documents() method in PyMongo. This is a handy method for when you just need to count the number of documents matching along a specific field, versus having to build a full aggregation pipeline with $match and $count to get that result.
Next, we get the number of UFO reports for that city/state combination using our get_count() helper method and a list of UFO sightings for that area with the get_ufos() helper function. Then the number of sightings in that state is assigned to the variable state_count.
The number of sightings in that city/state combination is returned as a cursor with PyMongo, which we can later iterate over using a Jinja for loop. And finally, we get a cursor we’ll name recent_ufos (which we can also use to iterate over the group of sightings the cursor points to) that points to a list of sightings for that city/state. Then state_count, city_count, and recent_ufos are passed to the results.html template to be displayed to the user.

Create results.html

Now that we have our html page where users can input their city and state, a function in our app.py file to process that information from the user, and some helper functions to support looking up the relevant data, we’re ready to display results for the user. The results.html page is displayed as the result of a successful POST request to the city() function in app.py. This page extends the layout.html template and shows statistics and information on sightings for the U.S. city and state input by the user.
We’ll display the value of state_count passed in from the city view function for the total sightings in the state input by the user on this page. Next, we’ll use a Jinja for loop to loop over our list of dictionaries pointed to by the city_count cursor, and pull the city dict's "ufo_count" value to display in the page.
We’ll also use a Jinja for loop to loop through the list of dictionaries pointed to by the recent_ufos cursor. As we go through the loop, we can pull the specific values we’re looking to display for the sightings using Python's usual syntax for looking up a value in a dict.
Here’s how this page looks after searching for UFO reports in Birmingham, Alabama:
Statistics and a list of UFO sightings for the U.S. city and state the website visitor submitted through the form

Create error.html

The last thing we’ll need to do in this section is create our error.html file, which the user sees if they don’t enter any text for the city name or a numeric or special character. This is a very simple html file with the error message passed in to it from app.py.
The next step we’ll take to build this out even further is adding functionality for users to input their own UFO sightings to the database for others to reference in the future.

Submit new UFO sighting and write to database

At this point, we have a fully functional Flask web application where users can submit a city and state, read from the MongoDB Atlas database, and get statistics and a list of sightings back. Now, let’s add functionality to write to the database with a function and supporting html pages that let users add their own UFO sightings.

Add a new function to App.py

Similar to the our city() function we’ve already created in app.py, our new submit() function will take both GET and POST requests.
The first thing we’ll set up in this function is processing our GET request. A GET request here leads to the display of the submit.html page, which will display a form with the relevant fields for the user to submit their sighting. We’ll pass in the array of U.S. states we used earlier for the state select field in the form.
For our POST request, the first thing we’ll do is create an empty Python dictionary called ufo_report to hold the report the user is submitting.
Next, we’ll use the request.form.get() function to pull the values the user added to the form and assign them to variables. Using date.today(), we can pull today’s month, day, and year to get the date of the report submission without the user having to manually enter it. Then, using today.strftime(), we put it in the right format to match the rest of the data set.
One important thing to note is that it is generally best practice to store date and time data with Python datetime.datetime objects (PyMongo will save these as native MongoDB date types) rather than as a string as we do here. However, for simplicity in working with the public data set from NUFORC, we will be storing this information as strings instead. This way, you won’t need to download the data from NUFORC and convert all of the strings from the data set into the proper object type for use in this tutorial.
Similar to our function that lets our users look up sightings by city and state, we’ll want to check for incorrect entries in the state field in the form. We’ll use isnumeric() to check if any numbers have been entered in the city name and a regular expression to check for special characters. We’ll then display an error message if anything is amiss.
Now that we have our variables pulled from the form, we will combine them in our empty python dictionary with the assignment operator and specify the appropriate keys for each value. Since this application only looks at UFO sightings in the U.S. (for now), we’ll manually set the country abbreviation as “us”. Once our dictionary for the UFO report is complete, we insert it into our ufos collection in our MongoDB Atlas database with ufos.insert_one(ufo_report).
After our report has been added, we’ll display a simple thank-you message to the user in thank_you.html.
If you wanted to do some extra credit, you could also model the latitude and longitude values as a GeoJSON Point type and index it for GIS queries. See how in the MongoDB documentation.

Create submit.html

Next, we’ll create the html page that’s displayed when the user submits a GET request to the submit() function. This page displays a form where the users can submit a record of their own UFO sighting. The design of the form is largely drawn from Bootstrap. As we did the city.html page, we’ll pass in the states array and create a Jinja loop to allow users to select a state from that array.
Here’s how this page looks when rendered:
A web page displaying a form where the website visitor can submit a report of a UFO sightings

Create Thank_You.html

This page uses Jinja to extend the layout.html template and display a simple (and fun) text thank-you message after the user successfully submits a UFO sighting report.


We may not be able to verify that aliens are out there in space. But we can track UFOs in cyberspace with MongoDB, Python, and Flask.
With the ease of use of Python and Flask, and the flexibility of MongoDB’s document model, we’ve now built an app to let users find statistics about UFO sightings in their area, get recent UFO sighting reports, and submit a sighting if they are lucky (or unlucky) enough to have an alien encounter of their own.
See the full codebase for this tutorial on GitHub. If you thought this material was valuable, be sure to rate this tutorial below and share a link on social!
Thank you to Shubham Ranjan for contributing edits and bug fixes to this tutorial and supporting code.

Facebook Icontwitter iconlinkedin icon
Rate this tutorial

Streaming Data with Apache Spark and MongoDB

Jul 11, 2023 | 7 min read

Using SuperDuperDB to Accelerate AI Development on MongoDB Atlas Vector Search

Feb 21, 2024 | 6 min read

Getting Started with MongoDB and AWS Codewhisperer

Apr 02, 2024 | 3 min read

How to Use PyMongo to Connect MongoDB Atlas with AWS Lambda

Apr 02, 2024 | 6 min read
Table of Contents