Overview of the ExpireEye backend system architecture, including FastAPI structure, services, routers, and background job scheduling
The ExpireEye backend is built on FastAPI, a modern Python web framework that provides high performance and automatic API documentation. The architecture follows a layered pattern with clear separation of concerns across routers, services, models, and database layers.
The notification_service.py manages WebSocket connections and real-time notifications:
app/services/notification_service.py
notification_connections = {}async def send_notification_to_user(user_id: str, message: dict): if user_id in notification_connections: try: await notification_connections[user_id].send_text(json.dumps(message)) except Exception as e: print(f"Error sending notification to user {user_id}: {e}") del notification_connections[user_id]
Users connect via WebSocket at /ws/notification and receive real-time updates when products expire or are scanned.
The notification service maintains an in-memory dictionary of active WebSocket connections, indexed by user ID for quick message routing.
The scheduler runs in the same process as the FastAPI application. For production deployments with multiple workers, consider using a separate worker process or distributed task queue like Celery to avoid duplicate job execution.
Custom middleware validates JWT tokens on every request from app/main.py:51-96:
@app.middleware("http")async def access_token_middleware(request: Request, call_next): # Skip preflight requests if request.method == "OPTIONS": return await call_next(request) public_paths = [ "/api/auth/login", "/api/auth/signup", "/api/status", "/docs", "/redoc", "/api/openapi.json", ] # Skip public paths if request.url.path in public_paths: return await call_next(request) # Extract and validate token auth_header = request.headers.get("Authorization") if not auth_header: return JSONResponse( status_code=401, content={"detail": "Authorization header missing or invalid."}, ) access_token = auth_header.split("Bearer ")[-1].strip() try: payload = decode_access_token(access_token) request.state.user = payload # Store user info in request state except Exception as e: return JSONResponse( status_code=401, content={"detail": "Access token is invalid."}, ) response = await call_next(request) return response
The middleware stores decoded user information in request.state.user, making it available to all endpoint handlers without repetitive authentication logic.
Clients connect with an access token as a query parameter for authentication. The connection is maintained for receiving real-time notifications about product expiration and scan events.
FastAPI provides automatic API documentation, type checking with Pydantic, async support, and excellent performance. It reduces boilerplate code and catches errors at development time rather than runtime.
Why separate routers and services?
This separation keeps endpoint handlers thin and focused on HTTP concerns while business logic lives in testable service functions. Services can be reused across multiple endpoints and are easier to unit test.
Why use APScheduler instead of cron?
APScheduler runs within the Python process, making it easier to share database connections and application context. It’s also more portable across different deployment environments.
Why validate tokens in middleware instead of dependencies?
Middleware runs before routing, allowing early rejection of unauthenticated requests. It also provides a single point of authentication logic rather than adding a dependency to every protected endpoint.