Build a Mars Photo Viewer with aiohttp and NASA API – Step-by-Step Guide

This tutorial walks you through creating a simple aiohttp web application that fetches random Mars rover photos from the NASA API, validates image quality using Pillow, and serves the images directly, covering environment setup, async code, and deployment options.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
Build a Mars Photo Viewer with aiohttp and NASA API – Step-by-Step Guide

Creating an aiohttp Application

Start by creating a new virtual environment (Python 3.5+ recommended) and install aiohttp: pip install aiohttp Create a file nasa.py with a basic handler that returns a text response:

from aiohttp import web

async def get_mars_photo(request):
    return web.Response(text='A photo of Mars')

app = web.Application()
app.router.add_get('/', get_mars_photo, name='mars_photo')

Run the app: web.run_app(app, host='127.0.0.1', port=8080) Alternatively, use aiohttp-devtools for automatic reload:

pip install aiohttp-devtools
adev runserver -p 8080 nasa.py

Using the NASA API

Replace the placeholder response with a call to the NASA Mars Rover Photos API. Each rover has a URL (Curiosity example shown) and requires at least sol (Martian day) and an API_KEY (use DEMO_KEY for testing).

import random
from aiohttp import web, ClientSession
from aiohttp.web import HTTPFound

NASA_API_KEY = 'DEMO_KEY'
ROVER_URL = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos'

async def get_mars_image_url_from_nasa():
    while True:
        sol = random.randint(0, 1722)  # max_sol at time of writing
        params = {'sol': sol, 'api_key': NASA_API_KEY}
        async with ClientSession() as session:
            async with session.get(ROVER_URL, params=params) as resp:
                resp_dict = await resp.json()
        if 'photos' not in resp_dict:
            raise Exception('API limit reached or error')
        photos = resp_dict['photos']
        if not photos:
            continue
        return random.choice(photos)['img_src']

async def get_mars_photo(request):
    url = await get_mars_image_url_from_nasa()
    return HTTPFound(url)

Running the Application

Execute the script with: python nasa.py Visiting http://localhost:8080 will display the text "A photo of Mars" initially.

Validating Images

To serve actual images, fetch the image bytes, validate size (and later color mode), and return the binary data.

pip install pillow
import io
from PIL import Image

async def validate_image(image_bytes):
    image = Image.open(io.BytesIO(image_bytes))
    return image.width >= 1024 and image.height >= 1024

Extended validation to reject grayscale images:

async def validate_image(image_bytes):
    image = Image.open(io.BytesIO(image_bytes))
    return (
        image.width >= 1024 and
        image.height >= 1024 and
        image.mode != 'L'
    )

Fetch image bytes and apply validation:

async def get_mars_photo_bytes():
    while True:
        image_url = await get_mars_image_url_from_nasa()
        async with ClientSession() as session:
            async with session.get(image_url) as resp:
                image_bytes = await resp.read()
        if await validate_image(image_bytes):
            break
    return image_bytes

async def get_mars_photo(request):
    image = await get_mars_photo_bytes()
    return web.Response(body=image, content_type='image/jpeg')

Complete Program

import random
import io
from aiohttp import web, ClientSession
from aiohttp.web import HTTPFound
from PIL import Image

NASA_API_KEY = 'DEMO_KEY'
ROVER_URL = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos'

async def validate_image(image_bytes):
    image = Image.open(io.BytesIO(image_bytes))
    return image.width >= 1024 and image.height >= 1024 and image.mode != 'L'

async def get_mars_image_url_from_nasa():
    while True:
        sol = random.randint(0, 1722)
        params = {'sol': sol, 'api_key': NASA_API_KEY}
        async with ClientSession() as session:
            async with session.get(ROVER_URL, params=params) as resp:
                resp_dict = await resp.json()
        if 'photos' not in resp_dict:
            raise Exception('API error')
        photos = resp_dict['photos']
        if not photos:
            continue
        return random.choice(photos)['img_src']

async def get_mars_photo_bytes():
    while True:
        image_url = await get_mars_image_url_from_nasa()
        async with ClientSession() as session:
            async with session.get(image_url) as resp:
                image_bytes = await resp.read()
        if await validate_image(image_bytes):
            break
    return image_bytes

async def get_mars_photo(request):
    image = await get_mars_photo_bytes()
    return web.Response(body=image, content_type='image/jpeg')

app = web.Application()
app.router.add_get('/', get_mars_photo, name='mars_photo')

Running this script will serve a random high‑resolution, non‑grayscale Mars photo each time the page is refreshed.

Mars photo example
Mars photo example
Another Mars photo
Another Mars photo
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

PythonWebAsyncpillowaiohttpimage validationNASA API
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.