A minimal FastAPI service backed by Google Gemini (google-genai).
It exposes a /chat endpoint that forwards prompts to Gemini, with the AI
provider hidden behind a small abstraction (AIPlatform) so other backends can
be added later.
src/
├── main.py # FastAPI app and routes
├── ai/
│ ├── base.py # AIPlatform abstract base class
│ └── gemini.py # Gemini implementation (google-genai Client)
├── auth/
│ ├── dependencies.py # JWT-based user identification (optional Bearer token)
│ └── throttling.py # in-memory per-user rate limiting
└── prompts/
└── system_prompt.md # system prompt prepended to every request
Requires Python 3.11+.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtDependency versions are pinned in requirements.txt for reproducible installs.
Required. A .env file is mandatory — the app reads its configuration from
it on startup and will refuse to start if the required variables are missing
(it raises ValueError for an unset GEMINI_API_KEY or JWT_SECRET_KEY). The
.env file is gitignored, so every clone must create its own:
cp .env.example .env
# then edit .env and fill in the required values (see the table below)Get a Gemini API key from https://aistudio.google.com/apikey
| Variable | Required | Default | Description |
|---|---|---|---|
GEMINI_API_KEY |
yes | — | Google Gemini API key. |
GEMINI_MODEL |
no | gemini-2.5-flash |
Model used for generation. |
JWT_SECRET_KEY |
yes | — | Secret used to verify JWTs on /chat. |
JWT_ALGORITHM |
no | HS256 |
Signing algorithm for JWT verification. |
Without a valid
.env(or the equivalent environment variables exported), the server will not start. The twoyesvariables above have no defaults by design.
Run from the project root so uvicorn can import the src package. The app
loads .env and the system prompt by paths relative to its own source, so they
resolve regardless of the current directory — and no manual environment export is
needed (python-dotenv handles it on startup):
source .venv/bin/activate
uvicorn src.main:app --reloadThe API is then available at http://127.0.0.1:8000 (interactive docs at /docs).
Liveness check.
curl http://127.0.0.1:8000/
# {"message":"API is running."}Send a prompt to Gemini and get the generated text back. The system prompt in
src/prompts/system_prompt.md is prepended automatically.
Request body
{ "prompt": "Say hello in three languages." }Response body
{ "response": "..." }Authentication & rate limiting
The Authorization header is optional:
- No token — requests are treated as a shared anonymous user, limited to 3 requests / 60s across all callers.
- Valid JWT (
HS256) — requests are limited per user (the token'ssubclaim) to 5 requests / 60s. - Invalid token —
401 Unauthorized.
Exceeding the limit returns 429 Too Many Requests.
Example (anonymous)
curl -X POST http://127.0.0.1:8000/chat \
-H "Content-Type: application/json" \
-d '{"prompt": "Say hello in three languages."}'Example (authenticated)
Pass a JWT in the Authorization: Bearer header. For testing you can generate a
token at https://www.jwt.io/ — use the HS256 algorithm with the secret from your
JWT_SECRET_KEY env var and a payload with a sub claim (the user id). Only sub
is read by the app; other claims are ignored. Example payload used for testing:
{
"sub": "testuser",
"name": "Santa Claus",
"iat": 1516239022
}curl -X POST "http://127.0.0.1:8000/chat" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer *****************************************" \
-d '{"prompt": "Why is the ocean blue?"}'Sample response
{
"response": "The ocean is blue because of how water absorbs and scatters sunlight ☀️🌊. Water absorbs red and yellow light more than blue light 🔴🟡. So, the blue light bounces around and gets scattered back to our eyes ✨💙!"
}The emoji-rich, plaintext style comes from the system prompt in
src/prompts/system_prompt.md.
Returns 503 if Gemini is temporarily unavailable.