Skip to content

Commit

Permalink
Merge pull request #41 from HackAtUCI/setup/api
Browse files Browse the repository at this point in the history
Configure API to work on Vercel from Next SSR
  • Loading branch information
samderanova authored Dec 2, 2023
2 parents daec239 + c116ab1 commit acfd79a
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 4 deletions.
1 change: 1 addition & 0 deletions apps/api/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
fastapi==0.104.1
python-multipart==0.0.5
4 changes: 4 additions & 0 deletions apps/api/src/app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from fastapi import FastAPI

from routers import demo

app = FastAPI()

app.include_router(demo.router, prefix="/demo", tags=["demo"])


@app.get("/")
async def root() -> dict[str, str]:
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
if __name__ == "__main__":
uvicorn.run(
"app:app",
host="localhost",
log_level="info",
access_log=True,
use_colors=True,
Expand Down
17 changes: 17 additions & 0 deletions apps/api/src/routers/demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import Annotated, Union

from fastapi import APIRouter, Cookie, Form

router = APIRouter()


@router.get("/me")
async def me(username: Annotated[Union[str, None], Cookie()] = None) -> str:
"""Who are you?"""
return f"You are {username or 'nobody'}"


@router.post("/square")
async def square(value: Annotated[int, Form()]) -> int:
"""Calculate the square of the value."""
return value * value
25 changes: 23 additions & 2 deletions apps/site/next.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
// All server-side API requests are handled by lib/utils/api.ts
// but this rewrite still exists for locally testing native POST requests
const LOCAL_API_URL = "http://localhost:8000";

// When deployed on Vercel, this path acts like a rewrite in vercel.json
// Next.js would normally be unable to find the API endpoint
// but Vercel somehow steps in and makes the Serverless Function visible
const VERCEL_API_PATH = "/api/";

/** @type {import('next').NextConfig} */
const nextConfig = {}
const nextConfig = {
rewrites: async () => {
return [
{
source: "/api/:path*",
destination:
process.env.NODE_ENV === "development"
? `${LOCAL_API_URL}/:path*`
: VERCEL_API_PATH,
},
];
},
};

module.exports = nextConfig
module.exports = nextConfig;
1 change: 1 addition & 0 deletions apps/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@react-three/fiber": "^8.15.11",
"@sanity/image-url": "^1.0.2",
"@types/three": "^0.158.2",
"axios": "^1.6.2",
"clsx": "^2.0.0",
"lucide-react": "^0.292.0",
"next": "13.5.6",
Expand Down
21 changes: 21 additions & 0 deletions apps/site/src/app/demo/Demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import api from "@/lib/utils/api";

async function getMessage(): Promise<string> {
const res = await api.get<string>("/demo/me");
return res.data;
}

async function Demo() {
const message = await getMessage();
return (
<div>
<p>Demo: {message}</p>
<form method="post" action="/api/demo/square">
<input type="number" required name="value" style={{ color: "black" }} />
<button type="submit">Submit</button>
</form>
</div>
);
}

export default Demo;
1 change: 1 addition & 0 deletions apps/site/src/app/demo/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as default } from "./Demo";
24 changes: 24 additions & 0 deletions apps/site/src/lib/utils/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import axios from "axios";
import { cookies } from "next/headers";

const LOCAL_API_URL = "http://localhost:8000";
const SERVER_HOST = process.env.NEXT_PUBLIC_VERCEL_URL;

// The Vercel Serverless Function for the API lives outside the scope of Next.js
// so the publicly deployed URL must be used instead of a rewrite
const api = axios.create({
baseURL: SERVER_HOST ? `https://${SERVER_HOST}/api/` : LOCAL_API_URL,
});

api.interceptors.request.use((config) => {
const cookieStore = cookies();

// Inject user's client-side cookies along with API request
const provided = config.headers.get("Cookie");
const newCookies = (provided ? `${provided}; ` : "") + cookieStore.toString();
config.headers.set("Cookie", newCookies);

return config;
});

export default api;
1 change: 0 additions & 1 deletion apps/site/vercel.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"buildCommand": "cd ../.. && turbo run build --filter={apps/site} && cd apps/site && ./copy-api.sh",
"rewrites": [{ "source": "/api/(.*)", "destination": "api/index.py" }],
"functions": {
"api/index.py": {
"memory": 512,
Expand Down
14 changes: 13 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit acfd79a

Please sign in to comment.