Using meilisearch with a fastapi frontend pip install fastapi uvicorn firebase-admin httpx
import os
import time
from typing import Optional , Dict , List
from fastapi import FastAPI , Header , HTTPException , status
from pydantic import BaseModel
import firebase_admin
from firebase_admin import credentials , auth
import httpx
# 1. Initialize Firebase Admin SDK for local, high-speed token verification
# Assumes GOOGLE_APPLICATION_CREDENTIALS environment variable is set
firebase_admin . initialize_app ()
app = FastAPI ()
# Configuration Settings from environment variables
MEILISEARCH_URL = os . getenv ( "MEILISEARCH_URL" , "http://127.0.0.1:7700" )
MEILI_SEARCH_KEY = os . getenv ( "MEILI_SEARCH_KEY" )
APP_SECRET_KEY = os . getenv ( "APP_SECRET_KEY" ) # Matches Flutter Envied key
# In-memory dictionary tracking user timestamps for rate limiting
# Key: uid (string), Value: List of timestamps (floats)
rate_limit_cache : Dict [ str , List [ float ]] = {}
# Define request schema validation using Pydantic
class SearchRequest ( BaseModel ):
q : str
limit : Optional [ int ] = 20
offset : Optional [ int ] = 0
filter : Optional [ str ] = ""
@app.post ( "/search" )
async def secure_search (
request_data : SearchRequest ,
x_app_key : Optional [ str ] = Header ( None ),
authorization : Optional [ str ] = Header ( None )
):
# 1. App Secret Validation (Blocks unauthorized API scripts)
if not x_app_key or x_app_key != APP_SECRET_KEY :
raise HTTPException (
status_code = status . HTTP_401_UNAUTHORIZED ,
detail = "Unauthorized Application Key"
)
# 2. Firebase Token Structure Check (Accepts both logged-in & anonymous tokens)
if not authorization or not authorization . startswith ( "Bearer " ):
raise HTTPException (
status_code = status . HTTP_401_UNAUTHORIZED ,
detail = "Missing or malformed session token"
)
id_token = authorization . split ( "Bearer " )[ 1 ]
try :
# Decodes and verifies the signature locally against public keys cached in memory
decoded_token = auth . verify_id_token ( id_token )
uid = decoded_token [ "uid" ]
except Exception :
raise HTTPException (
status_code = status . HTTP_401_UNAUTHORIZED ,
detail = "Invalid session token"
)
# 3. User-Based Rate Limiting (Using Firebase uid)
now = time . time ()
user_requests = rate_limit_cache . get ( uid , [])
# Filter out requests older than 1 minute (60 seconds)
active_requests = [ t for t in user_requests if now - t < 60.0 ]
if len ( active_requests ) >= 60 : # Limit: 60 searches per minute per user
raise HTTPException (
status_code = status . HTTP_429_TOO_MANY_REQUESTS ,
detail = "Too many search requests. Please slow down."
)
active_requests . append ( now )
rate_limit_cache [ uid ] = active_requests
# 4. Input Length Sanitisation
if len ( request_data . q ) > 100 :
raise HTTPException (
status_code = status . HTTP_400_BAD_REQUEST ,
detail = "Query string exceeds maximum length limit"
)
# 5. Non-blocking Asynchronous Request to local Meilisearch
async with httpx . AsyncClient () as client :
try :
meili_response = await client . post (
f " { MEILISEARCH_URL } /indexes/products/search" ,
headers = {
"Content-Type" : "application/json" ,
"Authorization" : f "Bearer { MEILI_SEARCH_KEY } "
},
json = {
"q" : request_data . q ,
"limit" : request_data . limit ,
"offset" : request_data . offset ,
"filter" : request_data . filter
},
timeout = 5.0
)
return meili_response . json ()
except httpx . RequestError :
raise HTTPException (
status_code = status . HTTP_500_INTERNAL_SERVER_ERROR ,
detail = "Search engine interface error"
)
uvicorn main:app --host 127.0.0.1 --port 8000 --workers 4
Ensure you adjust your header key layout in your Dart code slightly to match Python’s typical header parsing (x-app-key in Flutter maps straight to Python’s parameter variable x_app_key).