Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions python/starlette/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.vercel/
.venv/
venv/
__pycache__/
.Python
.DS_Store
.env*
.vercel
50 changes: 50 additions & 0 deletions python/starlette/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Starlette Starter

Deploy your [Starlette](https://www.starlette.io/) project to Vercel with zero configuration.

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?demo-description=Deploy%20Python%20Starlette%20applications%20with%20zero%20configuration.&demo-title=Starlette%20Boilerplate&demo-url=https%3A%2F%2Fvercel-plus-starlette.vercel.app%2F&from=templates&project-name=Starlette%20Boilerplate&repository-name=starlette-python-boilerplate&repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fexamples%2Ftree%2Fmain%2Fpython%2Fstarlette&skippable-integrations=1)

_Live Example: https://vercel-plus-starlette.vercel.app/_

Visit the [Starlette documentation](https://www.starlette.io/) to learn more.

## Getting Started

Install the required dependencies:

```bash
python -m venv .venv
source .venv/bin/activate
pip install .
```

Or, if using [uv](https://docs.astral.sh/uv/):

```bash
uv sync
```

## Running Locally

Start the development server on http://0.0.0.0:5001

```bash
python main.py
# using uv:
uv run main.py
```

When you make changes to your project, the server will automatically reload.

## Deploying to Vercel

Deploy your project to Vercel with the following command:

```bash
npm install -g vercel
vercel --prod
```

Or `git push` to your repository with our [git integration](https://vercel.com/docs/deployments/git).

To view the source code for this template, [visit the example repository](https://github.com/vercel/vercel/tree/main/examples/starlette).
3 changes: 3 additions & 0 deletions python/starlette/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .routes import api_routes

__all__ = ["api_routes"]
36 changes: 36 additions & 0 deletions python/starlette/api/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from starlette.responses import JSONResponse
from starlette.routing import Route


async def get_sample_data(request):
return JSONResponse(
{
"data": [
{"id": 1, "name": "Sample Item 1", "value": 100},
{"id": 2, "name": "Sample Item 2", "value": 200},
{"id": 3, "name": "Sample Item 3", "value": 300},
],
"total": 3,
"timestamp": "2024-01-01T00:00:00Z",
}
)


async def get_item(request):
item_id = int(request.path_params["item_id"])
return JSONResponse(
{
"item": {
"id": item_id,
"name": f"Sample Item {item_id}",
"value": item_id * 100,
},
"timestamp": "2024-01-01T00:00:00Z",
}
)


api_routes = [
Route("/data", get_sample_data),
Route("/items/{item_id:int}", get_item),
]
110 changes: 110 additions & 0 deletions python/starlette/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from starlette.applications import Starlette
from starlette.responses import HTMLResponse
from starlette.routing import Mount, Route
from api import api_routes


async def read_root(request):
return HTMLResponse("""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vercel + Starlette</title>
<link rel="icon" type="image/svg+xml" href="/favicon.ico">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
background-color: #000000; color: #ffffff; line-height: 1.6; min-height: 100vh;
display: flex; flex-direction: column;
}
header { border-bottom: 1px solid #333333; padding: 0; }
nav { max-width: 1200px; margin: 0 auto; display: flex; align-items: center; padding: 1rem 2rem; gap: 2rem; }
.logo { font-size: 1.25rem; font-weight: 600; color: #ffffff; text-decoration: none; }
.nav-links { display: flex; gap: 1.5rem; margin-left: auto; }
.nav-links a { text-decoration: none; color: #888888; padding: 0.5rem 1rem; border-radius: 6px; transition: all 0.2s ease; font-size: 0.875rem; font-weight: 500; }
.nav-links a:hover { color: #ffffff; background-color: #111111; }
main { flex: 1; max-width: 1200px; margin: 0 auto; padding: 4rem 2rem; display: flex; flex-direction: column; align-items: center; text-align: center; }
.hero { margin-bottom: 3rem; }
.hero-code { margin-top: 2rem; width: 100%; max-width: 900px; display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.hero-code pre { background-color: #0a0a0a; border: 1px solid #333333; border-radius: 8px; padding: 1.5rem; text-align: left; grid-column: 1 / -1; }
h1 { font-size: 3rem; font-weight: 700; margin-bottom: 1rem; background: linear-gradient(to right, #ffffff, #888888); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
.subtitle { font-size: 1.25rem; color: #888888; margin-bottom: 2rem; max-width: 600px; }
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; width: 100%; max-width: 900px; }
.card { background-color: #111111; border: 1px solid #333333; border-radius: 8px; padding: 1.5rem; transition: all 0.2s ease; text-align: left; }
.card:hover { border-color: #555555; transform: translateY(-2px); }
.card h3 { font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: #ffffff; }
.card p { color: #888888; font-size: 0.875rem; margin-bottom: 1rem; }
.card a { display: inline-flex; align-items: center; color: #ffffff; text-decoration: none; font-size: 0.875rem; font-weight: 500; padding: 0.5rem 1rem; background-color: #222222; border-radius: 6px; border: 1px solid #333333; transition: all 0.2s ease; }
.card a:hover { background-color: #333333; border-color: #555555; }
.status-badge { display: inline-flex; align-items: center; gap: 0.5rem; background-color: #0070f3; color: #ffffff; padding: 0.25rem 0.75rem; border-radius: 20px; font-size: 0.75rem; font-weight: 500; margin-bottom: 2rem; }
.status-dot { width: 6px; height: 6px; background-color: #00ff88; border-radius: 50%; }
pre { background-color: #0a0a0a; border: 1px solid #333333; border-radius: 6px; padding: 1rem; overflow-x: auto; margin: 0; }
code { font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; font-size: 0.85rem; line-height: 1.5; color: #ffffff; }
.keyword { color: #ff79c6; }
.string { color: #f1fa8c; }
.function { color: #50fa7b; }
.class { color: #8be9fd; }
.module { color: #8be9fd; }
.variable { color: #f8f8f2; }
.decorator { color: #ffb86c; }
@media (max-width: 768px) {
nav { padding: 1rem; flex-direction: column; gap: 1rem; }
.nav-links { margin-left: 0; }
main { padding: 2rem 1rem; }
h1 { font-size: 2rem; }
.hero-code { grid-template-columns: 1fr; }
.cards { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<header>
<nav>
<a href="/" class="logo">Vercel + Starlette</a>
<div class="nav-links">
<a href="/api/data">API</a>
</div>
</nav>
</header>
<main>
<div class="hero">
<h1>Vercel + Starlette</h1>
<div class="hero-code">
<pre><code><span class="keyword">from</span> <span class="module">starlette.applications</span> <span class="keyword">import</span> <span class="class">Starlette</span>
<span class="keyword">from</span> <span class="module">starlette.responses</span> <span class="keyword">import</span> <span class="class">JSONResponse</span>
<span class="keyword">from</span> <span class="module">starlette.routing</span> <span class="keyword">import</span> <span class="class">Route</span>

<span class="keyword">async def</span> <span class="function">homepage</span>(<span class="variable">request</span>):
<span class="keyword">return</span> <span class="class">JSONResponse</span>({<span class="string">"Python"</span>: <span class="string">"on Vercel"</span>})

<span class="variable">app</span> = <span class="class">Starlette</span>(<span class="variable">routes</span>=[<span class="class">Route</span>(<span class="string">"/"</span>, <span class="variable">homepage</span>)])</code></pre>
</div>
</div>

<div class="cards">
<div class="card">
<h3>Sample Data</h3>
<p>Access sample JSON data through our REST API. Perfect for testing and development purposes.</p>
<a href="/api/data">Get Data →</a>
</div>
</div>
</main>
</body>
</html>
""")


app = Starlette(
routes=[
Route("/", read_root),
Mount("/api", routes=api_routes),
]
)


if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=5001)
Binary file added python/starlette/public/favicon.ico
Binary file not shown.
7 changes: 7 additions & 0 deletions python/starlette/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[project]
name = "vercel-starlette-starter"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"starlette>=0.40"
]