A FULL HANDS-ON GUIDE
On this information, you’ll learn to bundle a easy TypeScript React Utility right into a Python bundle and serve it out of your FastAPI Python net server. Try the consumer and the server repos, if you wish to see the total code. Let’s get began!
In the course of the growth course of, you most likely use two totally different IDEs:
- TypeScript or JavaScript React App window, working on a devoted listening port (e.g., 5173) to serve the consumer/frontend pages.
- Python FastAPI, working on a special port (e.g., 8080) to serve a REST API.
In different phrases, you have got two totally different servers working domestically. Everytime you need to name your FastAPI server, the browser interacts with two totally different servers.
Whereas it really works high quality domestically (in localhost
), you’ll encounter a “Cross-Origin Request Blocked” error in your browser once you deploy that code. Earlier than taking your code to manufacturing, the very best observe is to serve each consumer pages and REST API from the identical backend net server. That method the browser will work together with a single backend. It’s higher for safety, efficiency, and ease.
1. Create a Easy React Utility
First, in your workspace
listing, let’s create a brand new TypeScript React software utilizing vite:
~/workspace ➜ npm create vite@newest
✔ Mission identify: … vite-project
✔ Choose a framework: › React
✔ Choose a variant: › TypeScript
Then, enter into the brand new venture listing, set up the dependencies, and run the applying (http://localhost:5173):
~/workspace ➜ cd vite-project
~/workspace/vite-project ➜ npm set up
~/workspace/vite-project ➜ npm run dev
You need to see one thing like:
Now, let’s make a small addition to the template — we’ll add an async HTTP name to the long run FastAPI backend to get its standing:
operate App() {
...
const [health, setHealth] = useState('');useEffect(() => {
const getStatus = async () => {
const response = await fetch('/v1/health-check/liveness', {
methodology: 'GET',
});
let standing: { [status: string]: string } = {};
attempt {
standing = await response.json();
} catch (err) {
console.log(`didn't get backend standing. ${err}`);
}
setHealth(standing['status'] || 'unknown');
};
getStatus();
}, []);
return (
...
<div>Backend Standing: {well being}</div>
...
)
}
And now we should always get one thing like this:
At this level, the Backend Standing is unknown
as a result of we haven’t applied it but. No worries, we are going to deal with that shortly. Lastly, let’s construct the consumer for packaging it afterward:
~/workspace/vite-project ➜ npm run construct
The construct output ought to create a dist
folder with the ultimate optimized code that appears like this:
└── dist/
├── belongings/
├── static/
└── index.html
2. Constructing a Python Package deal
At this level, we’re switching to Python. I favor to work in a digital surroundings for isolation. In a devoted digital surroundings, we are going to set up twine
and construct
, for creating our Python bundle:
~/workspace/vite-project ➜ python3 -m venv venv
~/workspace/vite-project ➜ . venv/bin/activate
~/workspace/vite-project (venv) ➜ python -m pip set up --upgrade pip
~/workspace/vite-project (venv) ➜ pip set up twine==5.0.0 construct==1.2.1
Let’s create a brand new setup.py
file within the root folder (vite-project
), with the next content material:
from setuptools import setup
from pathlib import Pathcwd = Path(__file__).mother or father
long_description = (cwd / "README.md").read_text()
setup(
identify="vite-project",
model="0.0.1",
package_dir={"vite_project": "dist"},
package_data={"vite_project": ["**/*.*"]},
long_description=long_description,
long_description_content_type="textual content/markdown",
)
and run the next to create the bundle:
~/workspace/vite-project (venv) ➜ python setup.py sdist -d tmp
~/workspace/vite-project (venv) ➜ python -m construct --wheel --outdir tmp
~/workspace/vite-project (venv) ➜ twine add -u ${USERNAME} -p ${PASSWORD} --repository-url ${REPO_URL} tmp/*
The final line above is non-obligatory in case you intend to add your bundle to a distant repository resembling PyPI, JFrog Artifactory, and so on.
3. Create a FastAPI Python web-server
The ultimate step is to construct the Python server and use the consumer bundle. For that, we are going to:
- Create a brand new
backend
listing. - Create a brand new digital surroundings.
- Set up the related packages and our consumer bundle:
~/workspace/backend ➜ python3 -m venv venv
~/workspace/backend ➜ . venv/bin/activate
~/workspace/backend (venv) ➜ python -m pip set up --upgrade pip
~/workspace/backend (venv) ➜ pip set up fastapi==0.110.0 uvicorn==0.29.0
~/workspace/backend (venv) ➜ pip set up ~/workspace/vite-project/tmp/vite-project-0.0.1.tar.gz
Word that we put in our consumer bundle from an area path that we created earlier. When you uploaded your bundle to a distant repository, you possibly can set up it with:
~/workspace/backend (venv) ➜ pip set up --extra-index-url https://${USERNAME}:${PASSWORD}@${REPO_URL} vite-project==0.0.1
Subsequent, let’s create a easy Python server (2 information):
__main__.py
from distutils.sysconfig import get_python_lib
from fastapi import FastAPI
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from backend.health_router import router
from uvicorn import rundef create_app():
app = FastAPI(
title="Backend Server",
)
app.include_router(router)
client_path = f"{get_python_lib()}/vite_project"
app.mount("/belongings", StaticFiles(listing=f"{client_path}/belongings"), identify="belongings")
app.mount("/static", StaticFiles(listing=f"{client_path}/static"), identify="static")
@app.get("/{catchall:path}")
async def serve_react_app(catchall: str):
return FileResponse(f"{client_path}/index.html")
return app
def principal():
app = create_app()
run(app, host="0.0.0.0", port=8080)
if __name__ == "__main__":
principal()
health_router.py
from typing import Literal
from typing_extensions import TypedDict
from fastapi import APIRouter, standingSTATUS = Literal["success", "error", "partial", "unknown"]
class ReturnHealthcheckStruct(TypedDict):
standing: STATUS
router = APIRouter(
prefix="/v1/health-check",
tags=["Health Check"],
)
@router.get(
"/liveness",
abstract="Carry out a Liveness Well being Test",
response_description="Return HTTP Standing Code 200 (OK)",
status_code=standing.HTTP_200_OK,
response_model=ReturnHealthcheckStruct,
)
async def liveness() -> ReturnHealthcheckStruct:
return {"standing": "success"}
Within the implementation above, we added help for serving any static file from our consumer software by mounting the static
and belongings
folders, in addition to another consumer file to be served by our Python server.
We additionally created a easy GET endpoint, v1/health-check/liveness
that returns a easy {“standing": “success"}
JSON response. That method we are able to be sure that our server handles each consumer static information and our server-side RESTful API.
Now, if we go to localhost:8080 we are able to see our consumer up and working. Take note of the Backend Standing beneath, it’s now success
(reasonably than unknown
).
On this tutorial, we created a easy React Utility that does a single name to the backend. We wrapped this consumer software as a Python bundle and served it from our FastAPI Python net server.
Utilizing this strategy lets you leverage the very best instruments in each worlds: TypeScript and React for the frontend, and Python with FastAPI for the backend. But, we need to maintain excessive cohesion and low coupling between these two elements. That method, you’ll get all the advantages:
- Velocity, by separating front-end and backend to totally different repositories, every half might be developed by a special staff.
- Stability and High quality, by locking a versioned consumer bundle and bumping it solely when the server is able to help a brand new consumer model.
- Security — The browser interacts with just one backend server. We don’t have to allow CORS or another security-compromising workarounds.
- Simplicity — By working through a single server