A quick 45‑minute guide to a freehire‑inspired job board using Python & SQLite
Welcome to another official UVF IT build guide! In this tutorial we’ll create a tiny job‑search engine inspired by freehire, pulling static sample data and exposing a tiny API you can run locally.
You’ll get a working Flask app with a SQLite store, a single endpoint to list jobs, and a minimal HTML page – all in under an hour and without any paid services.
pip; older versions may miss typing features used here.First we set up an isolated workspace so our dependencies don’t clash with other Python projects. A virtual environment gives us a clean Python interpreter, and installing Flask and SQLAlchemy provides the web framework and ORM we’ll need to store jobs.
Next we create the folder structure that will hold our code, a requirements file for reproducibility, and a tiny jobs.json that will act as our seed data source.
All of these steps are one‑liners, but running them in order ensures the rest of the guide works without surprises.
mkdir freehire_clone && cd freehire_clone
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install Flask SQLAlchemy
mkdir static templates
cat > requirements.txt <<'EOF'
Flask==2.3.2
SQLAlchemy==2.0.23
EOFWe now describe the shape of a job posting with SQLAlchemy. The Job class maps to a SQLite table and includes fields like title, company, location, and a short description. After defining the model we load a few sample jobs from a jobs.json file so the API has something to return right away.
The seed step runs once at startup: it reads the JSON, creates Job objects, and commits them to the database if the table is empty. This keeps the example stateless and easy to reset by simply deleting the app.db file.
models.py
import json
from pathlib import Path
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
Base = declarative_base()
class Job(Base):
__tablename__ = "jobs"
id = Column(Integer, primary_key=True)
title = Column(String, nullable=False)
company = Column(String, nullable=False)
location = Column(String, nullable=False)
description = Column(String, nullable=False)
# SQLite engine – file will be created in the project root
engine = create_engine('sqlite:///app.db', echo=False, future=True)
Session = sessionmaker(bind=engine)
def init_db():
Base.metadata.create_all(engine)
session = Session()
# Seed only if table is empty
if session.query(Job).first() is None:
seed_path = Path(__file__).parent / "static" / "jobs.json"
if seed_path.exists():
with open(seed_path, "r", encoding="utf-8") as f:
jobs_data = json.load(f)
for item in jobs_data:
job = Job(
title=item.get("title", "Untitled"),
company=item.get("company", "Unknown"),
location=item.get("location", "Remote"),
description=item.get("description", "")
)
session.add(job)
session.commit()
session.close()
if __name__ == "__main__":
init_db()
print("Database initialized and seeded.")In this step we create a tiny Flask web server that reads a static jobs.json file and serves two routes. The /jobs endpoint returns the raw job data as JSON, which is useful for programmatic access. The root (/) route renders a minimal HTML page that loops over the jobs and displays each title as a list item, giving you a quick visual check of the data.
We keep everything in a single app.py file so the project stays lightweight. By loading the JSON once at startup we avoid repeated disk I/O, and Flask’s built‑in templating lets us embed the list without any extra front‑end libraries.
app.py
import json
from flask import Flask, jsonify, render_template_string
app = Flask(__name__)
# Load static job data once when the app starts
with open('jobs.json', 'r', encoding='utf-8') as f:
JOBS = json.load(f)
# Simple HTML template for the home page
HTML_TEMPLATE = """
<!doctype html>
<html lang='en'>
<head><meta charset='utf-8'><title>Job List</title></head>
<body>
<h1>Available Jobs</h1>
<ul>
{% for job in jobs %}
<li>{{ job['title'] }}</li>
{% endfor %}
</ul>
</body>
</html>
"""
@app.route('/')
def home():
return render_template_string(HTML_TEMPLATE, jobs=JOBS)
@app.route('/jobs')
def get_jobs():
return jsonify(JOBS)
if __name__ == '__main__':
app.run(debug=True)Now we start the Flask development server and verify both the human‑readable UI and the raw API work as expected. The server runs on http://127.0.0.1:5000/ by default. Open that address in a browser to see the list of job titles, and use curl to fetch the JSON payload from the /jobs endpoint.
If anything looks off, check the console output for import errors or missing jobs.json. The debug=True flag will also reload the app automatically when you make changes, which is handy during development.
# Install dependencies if you haven't already
pip install flask
# Run the Flask app
python app.py &
# Give the server a moment, then test the API endpoint
curl http://127.0.0.1:5000/jobs
# Open the UI in your default browser (Linux/macOS example)
xdg-open http://127.0.0.1:5000 || open http://127.0.0.1:5000To make the clone a bit more useful, we add a simple query parameter ?q= that filters jobs by title. This keeps the API lightweight while demonstrating how you can grow the service later (e.g., adding pagination or full‑text search). The filter is case‑insensitive and returns all jobs when the parameter is omitted.
The change lives in the same app.py file; we just adjust the /jobs route to look for the q argument, apply the filter, and return the matching subset.
app.py
import json
from flask import Flask, jsonify, render_template_string, request
app = Flask(__name__)
with open('jobs.json', 'r', encoding='utf-8') as f:
JOBS = json.load(f)
HTML_TEMPLATE = """
<!doctype html>
<html lang='en'>
<head><meta charset='utf-8'><title>Job List</title></head>
<body>
<h1>Available Jobs</h1>
<ul>
{% for job in jobs %}
<li>{{ job['title'] }}</li>
{% endfor %}
</ul>
</body>
</html>
"""
@app.route('/')
def home():
return render_template_string(HTML_TEMPLATE, jobs=JOBS)
@app.route('/jobs')
def get_jobs():
query = request.args.get('q', default='', type=str).lower()
if query:
filtered = [job for job in JOBS if query in job.get('title', '').lower()]
else:
filtered = JOBS
return jsonify(filtered)
if __name__ == '__main__':
app.run(debug=True)| Check | Command / Why it matters |
|---|---|
| Flask server starts without errors | python app.py → should display *Running on http://127.0.0.1:5000/* |
| /jobs returns JSON array | curl http://127.0.0.1:5000/jobs → non‑empty list |
| Browser shows job titles | Open http://127.0.0.1:5000 → visible list of sample jobs |
Your minimal job‑search clone is up and running. Feel free to replace the static jobs.json with a real scraper or deploy the Flask app to a free cloud host for a fuller freehire experience.