DEV Community

Elias Benfridja
Elias Benfridja

Posted on

How I Built a Visa Tracker with Django, Aurora PostgreSQL, and react

Visa applications are stressful. You have dozens of documents to collect, country-specific requirements to meet, and strict deadlines to hit — all with zero visibility into where you actually stand. I built VisaTrack to fix that: an AI-powered document tracker that generates your required checklist automatically, lets you track progress, upload files, and get guidance from an AI advisor.

I built it for the H0 — Hack the Zero Stack with Vercel v0 & AWS Databases hackathon. Here's how it came together.

The Stack

LayerTechnologyFrontendReact, TypeScript, Tailwind CSSBackendDjango, Django REST FrameworkDatabaseAmazon Aurora PostgreSQL (Serverless v2)AIGoogle Gemini 2.5 FlashFrontend HostingVercelBackend HostingRailwayCloudAWS (eu-north-1)

The idea was simple: user picks a destination country and visa purpose, Gemini generates the document checklist, and the user tracks their progress in a clean UI. Pro users get access to a conversational AI advisor that answers questions specific to their application.

Why Aurora PostgreSQL

The hackathon required one of three AWS databases: Aurora PostgreSQL, Aurora DSQL, or DynamoDB.

DynamoDB was out immediately — it's a NoSQL key-value store and Django's ORM is relational by design. You'd have to throw out migrations, the admin panel, and most of DRF's generic views.

Aurora DSQL looked promising until I read the docs. It's a distributed SQL engine launched in late 2024 and still in preview. Critical limitation: no foreign keys, no sequences. Django's auto-increment primary keys rely on sequences, so models break before you even run your first migration.

Aurora PostgreSQL was the clear choice. It's fully compatible with PostgreSQL, which means Django's ORM, migrations, and Django Admin all work out of the box. The serverless v2 configuration auto-scales between 0 and 4 ACUs based on demand — perfect for a hackathon project where traffic is unpredictable.

The Hardest Part: IAM Authentication

This is where things got interesting.

I created the Aurora cluster using AWS's "Express Configuration" — a one-click setup that spins up a serverless cluster in seconds. What I didn't realize was that express config automatically enables Internet Access Gateway and IAM Database Authentication, and those two features are permanently locked together. You cannot use password authentication when the Internet Access Gateway is enabled.

So when I tried connecting Django to Aurora with a standard DATABASE_URL, every connection attempt failed with:

FATAL: PAM authentication failed for user "postgres"

The solution was to build a custom Django database backend that generates short-lived IAM tokens on every connection using boto3 instead of using a static password.

Here's the backend I wrote at config/db_backends/iam_auth/base.py:

pythonimport boto3
import os
from django.db.backends.postgresql import base

class DatabaseWrapper(base.DatabaseWrapper):
def get_connection_params(self):
params = super().get_connection_params()
client = boto3.client(
'rds',
region_name='eu-north-1',
aws_access_key_id=os.environ.get('AWS_ACCESS_KEY_ID'),
aws_secret_access_key=os.environ.get('AWS_SECRET_ACCESS_KEY'),
)
token = client.generate_db_auth_token(
DBHostname=params['host'],
Port=int(params['port']),
DBUsername=params['user']
)
params['password'] = token
params['sslmode'] = 'require'
return params

And the Django settings:

pythonDATABASES = {
'default': {
'ENGINE': 'config.db_backends.iam_auth',
'NAME': 'postgres',
'USER': 'postgres',
'HOST': 'visatrack-db.cluster-xxxx.eu-north-1.rds.amazonaws.com',
'PORT': '5432',
'CONN_MAX_AGE': 0, # important — forces a fresh token on every connection
}
}

Django database backends are expected to be Python packages with a base.py file — that's why the path is iam_auth/base.py and not just iam_auth.py. The ENGINE string tells Django where to find the DatabaseWrapper class.

CONN_MAX_AGE must be 0 here. IAM tokens expire after 15 minutes, so persistent connections would eventually fail with an expired token. Setting it to 0 forces Django to generate a fresh token on every request.

On the Railway side, I set three environment variables:

AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_DEFAULT_REGION=eu-north-1

The IAM user has AmazonRDSFullAccess and an inline policy allowing rds-db:connect. Once that was in place, migrations ran cleanly and the app was live.

Frontend on Vercel

The React frontend was straightforward to deploy on Vercel. Connect the repo, set the root directory to Frontend/, and Vercel handles the rest. The only configuration needed was the backend API URL as an environment variable:

VITE_API_URL=https://application-tracker-production-3af2.up.railway.app

Vercel's preview deployments were genuinely useful during development — every push to a branch got its own URL for testing without touching production.

The AI Layer

Every time a user creates a new application, the backend calls Gemini 2.5 Flash with a prompt that includes the destination country and visa purpose. Gemini returns a JSON array of required documents — each with a name, description, and tips on how to obtain it. These get saved to the database and rendered as the user's checklist.

For Pro users, a chat endpoint passes the user's question along with their application context (country, purpose, document list) to Gemini and streams back a practical answer. The AI stays focused on visa topics — if someone asks something unrelated, it redirects them back to their application.

What I Learned

Aurora express config has hidden constraints. The one-click setup is convenient but locks you into IAM authentication. If you need password-based auth, use Standard Create and configure it manually.

IAM auth is actually better once it's working. No static credentials to rotate, no passwords to leak. Short-lived tokens generated per-connection is a genuinely more secure pattern than a DATABASE_URL sitting in an environment variable.

Serverless v2 cold starts are real. After a period of inactivity the cluster scales to 0 ACUs and the first request takes a few seconds longer. For a production app you'd want a minimum capacity of 0.5 ACUs to avoid this.

Django's pluggable database backend system is powerful. I didn't know you could swap out the database backend with a custom class until this project. It's a clean extension point that made the IAM token solution possible without touching any application code.

Try It

Live demo: https://application-tracker-jet.vercel.app
Backend API: https://application-tracker-production-3af2.up.railway.app

I built VisaTrack for the H0 — Hack the Zero Stack with Vercel v0 & AWS Databases hackathon. If you're building something similar or have questions about the Aurora IAM setup, drop a comment below.

Top comments (0)