Khi FastAPI cần phục vụ file tĩnh — ảnh, CSS, JavaScript hay bản build frontend — cách đúng là gắn StaticFiles bằng app.mount() một lần thay vì viết một endpoint cho mỗi file. StaticFiles tự lo việc đọc file, đặt Content-Type theo đuôi file và trả 404 khi file không tồn tại. Cách này áp dụng khi bạn phục vụ tài sản tĩnh của chính ứng dụng, từ thư mục riêng biệt, không chứa mã nguồn hay file cấu hình nhạy cảm.
Vấn đề
Một ứng dụng nhỏ thường bắt đầu bằng một endpoint trả file logo, rồi thêm endpoint cho favicon, file CSS, từng ảnh. Mỗi file mới là một hàm mới, một route mới, và đổi tên file lại phải sửa code. Tệ hơn, nếu tự đọc file bằng open() rồi trả Response, bạn phải tự lo Content-Type, chặn đường dẫn ../ thoát khỏi thư mục và xử lý lỗi khi file không tồn tại — những việc StaticFiles đã làm sẵn.
from fastapi import FastAPIfrom fastapi.responses import FileResponseapp = FastAPI()@app.get("/assets/logo.png")def logo(): return FileResponse("static/logo.png")@app.get("/assets/style.css")def style(): return FileResponse("static/style.css")
Giải pháp
Gắn StaticFiles một lần, mọi file trong thư mục đều có URL ngay mà không cần thêm route:
from fastapi import FastAPIfrom fastapi.staticfiles import StaticFilesapp = FastAPI()app.mount("/static", StaticFiles(directory="static"), name="static")
Giờ file static/logo.png có URL /static/logo.png, và file mới copy vào thư mục là truy cập được ngay, không cần sửa code hay restart server. File không tồn tại trả 404, còn yêu cầu kiểu /static/../app.py cũng bị chặn 404 vì đường dẫn sau khi giải quyết phải nằm trong thư mục đã cấu hình.
Hai lưu ý quan trọng. Thứ nhất, thứ tự mount: route API đăng ký trước, mount ở / để cuối cùng — nếu không mount / sẽ “nuốt” cả route API vì route được kiểm tra theo thứ tự đăng ký. Với html=True, StaticFiles trả index.html khi truy cập đường dẫn thư mục, và trả file 404.html (nếu có) cho đường dẫn không khớp file:
@app.get("/api/ping")def ping(): return {"ok": True}app.mount("/static", StaticFiles(directory="static"), name="static")app.mount("/", StaticFiles(directory="frontend/dist", html=True), name="frontend")
Thứ hai, thư mục phải tồn tại lúc khởi động: StaticFiles mặc định ném RuntimeError ngay nếu thư mục chưa có, thay vì âm thầm trả 404 mọi request. Chỉ truyền check_dir=False khi thư mục được tạo động lúc chạy.
Áp dụng vào dự án của bạn
Trong dự án thật, hãy tách riêng một thư mục static chỉ chứa tài sản công khai, và tuyệt đối không mount thư mục gốc của dự án: mount nhầm sẽ phơi bày mã nguồn, file .env và cấu hình ra Internet. Một bố cục hay gặp: API giữ nguyên dưới /api, thư mục build của frontend (ví dụ frontend/dist) mount ở / với html=True để trang chủ phục vụ được ngay, và thêm 404.html trỏ về entry của SPA nếu cần hỗ trợ route con phía client. Với file lớn hoặc lượng truy cập cao, đặt Nginx hoặc CDN phía trước để phục vụ thư mục tĩnh, FastAPI chỉ lo API.
Bài viết liên quan
- FastAPI: render trang HTML bằng Jinja2 TemplateResponse trong 5 phút
- FastAPI: cho phép client tải file an toàn bằng FileResponse, chặn path traversal trong 5 phút
- FastAPI: viết dependency dạng class — gom tham số chung và logic tái sử dụng trong 5 phút
Câu hỏi thường gặp
StaticFiles khác gì FileResponse?
FileResponse trả một file cụ thể trong một endpoint, tiện khi cần kiểm soát quyền truy cập từng file bằng dependency xác thực. StaticFiles phục vụ toàn bộ thư mục qua một lần mount, hợp khi file đều công khai và nhiều.
File không tồn tại thì StaticFiles trả gì?
Trả 404. Riêng ở chế độ html=True, đường dẫn thư mục trả index.html, còn đường dẫn không khớp file trả 404.html nếu có, nếu không vẫn là 404 trơn.
Thêm file mới có cần restart server không?
Không. StaticFiles đọc file theo từng request nên file mới copy vào thư mục là truy cập được ngay.
Dùng StaticFiles cho production được không?
Được với ứng dụng nhỏ và vừa. Khi traffic lớn, phục vụ file bằng Python tốn worker của ứng dụng, nên để Nginx hoặc CDN phục vụ thư mục tĩnh thay thế.
💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.
Nguồn tham khảo
- FastAPI — Static Files: https://fastapi.tiangolo.com/tutorial/static-files/
- Starlette — StaticFiles reference: https://fastapi.tiangolo.com/reference/staticfiles/
