Skip to content

Commit a43d079

Browse files
committed
fix: add neon integration
1 parent 777046a commit a43d079

File tree

9 files changed

+224
-0
lines changed

9 files changed

+224
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Setting up Neon
2+
3+
- Set the `DATABASE_URL` in your `.env`.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Neon database URL
2+
DATABASE_URL=
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- Schema for a simple to-do list
2+
CREATE TABLE IF NOT EXISTS todos (
3+
id SERIAL PRIMARY KEY,
4+
title VARCHAR(255) NOT NULL,
5+
description TEXT,
6+
is_completed BOOLEAN DEFAULT FALSE,
7+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
8+
);
9+
10+
-- Initial data for the to-do list
11+
INSERT INTO todos (title, description, is_completed) VALUES
12+
('Buy groceries', 'Milk, Bread, Eggs, and Butter', FALSE),
13+
('Read a book', 'Finish reading "The Great Gatsby"', FALSE),
14+
('Workout', 'Go for a 30-minute run', FALSE);
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { neon } from '@neondatabase/serverless'
2+
3+
let client: ReturnType<typeof neon>
4+
5+
export async function getClient() {
6+
if (!process.env.DATABASE_URL) {
7+
return undefined
8+
}
9+
if (!client) {
10+
client = await neon(process.env.DATABASE_URL!)
11+
}
12+
return client
13+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { createServerFn } from '@tanstack/react-start'
2+
import { createFileRoute, useRouter } from '@tanstack/react-router'
3+
4+
import { getClient } from '@/db'
5+
6+
const getTodos = createServerFn({
7+
method: 'GET',
8+
}).handler(async () => {
9+
const client = await getClient()
10+
if (!client) {
11+
return undefined
12+
}
13+
return (await client.query(`SELECT * FROM todos`)) as Array<{
14+
id: number
15+
title: string
16+
}>
17+
})
18+
19+
const insertTodo = createServerFn({
20+
method: 'POST',
21+
})
22+
.validator((d: { title: string }) => d)
23+
.handler(async ({ data }) => {
24+
const client = await getClient()
25+
if (!client) {
26+
return undefined
27+
}
28+
await client.query(`INSERT INTO todos (title) VALUES ($1)`, [data.title])
29+
})
30+
31+
export const Route = createFileRoute('/demo/neon')({
32+
component: App,
33+
loader: async () => {
34+
const todos = await getTodos()
35+
return { todos }
36+
},
37+
})
38+
39+
function App() {
40+
const { todos } = Route.useLoaderData()
41+
const router = useRouter()
42+
43+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
44+
e.preventDefault()
45+
const formData = new FormData(e.target as HTMLFormElement)
46+
const data = Object.fromEntries(formData)
47+
await insertTodo({ data: { title: data.title as string } })
48+
router.invalidate()
49+
}
50+
51+
if (!todos) {
52+
return <DBConnectionError />
53+
}
54+
55+
return (
56+
<div
57+
className="flex items-center justify-center min-h-screen bg-gradient-to-br from-purple-100 to-blue-100 p-4 text-white"
58+
style={{
59+
backgroundImage:
60+
'radial-gradient(circle at 5% 40%, #63F655 0%, #00E0D9 40%, #1a0f0a 100%)',
61+
}}
62+
>
63+
<div className="w-full max-w-2xl p-8 rounded-xl backdrop-blur-md bg-black/50 shadow-xl border-8 border-black/10">
64+
<div className="flex items-center justify-center gap-4 mb-8 bg-black/30 p-4 rounded-lg">
65+
<div className="relative">
66+
<div className="absolute -inset-1 bg-gradient-to-r from-emerald-400 to-cyan-400 rounded-lg blur opacity-75 group-hover:opacity-100 transition duration-1000"></div>
67+
<div className="relative">
68+
<img
69+
src="/demo-neon.svg"
70+
alt="Neon Logo"
71+
className="w-12 h-12 transform hover:scale-110 transition-transform duration-200"
72+
/>
73+
</div>
74+
</div>
75+
<h1 className="text-3xl font-bold bg-gradient-to-r from-emerald-200 to-cyan-200 text-transparent bg-clip-text">
76+
Neon Database Demo
77+
</h1>
78+
</div>
79+
{todos && (
80+
<>
81+
<h1 className="text-2xl font-bold mb-4">Todos</h1>
82+
<ul className="space-y-3 mb-6">
83+
{todos.map((todo: { id: number; title: string }) => (
84+
<li
85+
key={todo.id}
86+
className="bg-white/10 backdrop-blur-sm rounded-lg p-4 shadow-sm border border-white/20 transition-all hover:bg-white/20 hover:scale-[1.02] cursor-pointer group"
87+
>
88+
<div className="flex items-center justify-between">
89+
<span className="text-lg font-medium group-hover:text-white/90">
90+
{todo.title}
91+
</span>
92+
<span className="text-xs text-white/50">#{todo.id}</span>
93+
</div>
94+
</li>
95+
))}
96+
</ul>
97+
<form onSubmit={handleSubmit} className="mt-4 flex gap-2">
98+
<input
99+
type="text"
100+
name="title"
101+
className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#00E0D9] bg-black/20"
102+
/>
103+
<button
104+
type="submit"
105+
className="px-6 py-2 bg-[#00E0D9] text-black font-medium rounded-md hover:bg-[#00E0D9]/80 focus:outline-none focus:ring-2 focus:ring-[#00E0D9] focus:ring-offset-2 transition-colors disabled:opacity-50 whitespace-nowrap"
106+
>
107+
Add Todo
108+
</button>
109+
</form>
110+
</>
111+
)}
112+
</div>
113+
</div>
114+
)
115+
}
116+
117+
function DBConnectionError() {
118+
return (
119+
<div className="text-center space-y-6">
120+
<div className="flex items-center justify-center mb-4">
121+
<svg
122+
className="w-12 h-12 text-amber-500"
123+
fill="none"
124+
stroke="currentColor"
125+
viewBox="0 0 24 24"
126+
xmlns="http://www.w3.org/2000/svg"
127+
>
128+
<path
129+
strokeLinecap="round"
130+
strokeLinejoin="round"
131+
strokeWidth="2"
132+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
133+
/>
134+
</svg>
135+
</div>
136+
<h2 className="text-2xl font-bold mb-4">Database Connection Issue</h2>
137+
<div className="text-lg mb-6">The Neon database is not connected.</div>
138+
<div className="bg-black/30 p-6 rounded-lg max-w-xl mx-auto">
139+
<h3 className="text-lg font-semibold mb-4">Required Steps to Fix:</h3>
140+
<ul className="space-y-4 text-left list-none">
141+
<li className="flex items-start">
142+
<span className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-amber-500 text-black font-bold mr-3 min-w-8 min-h-8">
143+
1
144+
</span>
145+
<div>
146+
Use the{' '}
147+
<code className="bg-black/30 px-2 py-1 rounded">db/init.sql</code>{' '}
148+
file to create the database
149+
</div>
150+
</li>
151+
<li className="flex items-start">
152+
<span className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-amber-500 text-black font-bold mr-3 min-w-8 min-h-8">
153+
2
154+
</span>
155+
<div>
156+
Set the{' '}
157+
<code className="bg-black/30 px-2 py-1 rounded">
158+
DATABASE_URL
159+
</code>{' '}
160+
environment variable to the connection string of your Neon
161+
database
162+
</div>
163+
</li>
164+
</ul>
165+
</div>
166+
</div>
167+
)
168+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "Neon",
3+
"description": "Add the Neon database to your application.",
4+
"link": "https://neon.tech",
5+
"phase": "add-on",
6+
"type": "add-on",
7+
"modes": ["file-router"],
8+
"routes": [
9+
{
10+
"url": "/demo/neon",
11+
"name": "Neon",
12+
"path": "src/routes/demo.neon.tsx",
13+
"jsName": "NeonDemo"
14+
}
15+
],
16+
"dependsOn": ["start"]
17+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dependencies": {
3+
"@neondatabase/serverless": "^1.0.0"
4+
}
5+
}
Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)