Introduction
Getting started with TanStack Start
#Automatic Setup With Create Instant App
The fastest way to get started with Instant on TanStack Start is to use npx create-instant-app to scaffold a new project with Instant already set up.
To get started run:
npx create-instant-app -b tanstack-start
#Manual Setup
The following guide demonstrates the basics for manually scaffolding a new tanstack project with Instant and managing data. In general we recommend the automatic setup above which includes all of this and a basic auth flow and other useful full-stack utilities.
Create a blank TanStack Start app:
npx @tanstack/cli create my-app
Add the InstantDB React Library:
npm i @instantdb/react
Use instant-cli to set up a new Instant project. This prompt you to log in if you haven't already., It will then create a schema file, permissions file, and update your .env file.
npx instant-cli init
Create a database client in src/lib/db.ts:
import { init } from '@instantdb/react';import schema from '../instant.schema';export const db = init({appId: process.env.VITE_INSTANT_APP_ID!,schema,useDateObjects: true,});
You're now ready to make queries and transactions to your database!
#Creating a To-Do List App
Let's add a "todo" entity to our schema file at src/instant.schema.ts:
import { i } from '@instantdb/react';const _schema = i.schema({entities: {$files: i.entity({path: i.string().unique().indexed(),url: i.string(),}),$users: i.entity({email: i.string().unique().indexed().optional(),imageURL: i.string().optional(),type: i.string().optional(),}),todos: i.entity({text: i.string(),done: i.boolean(),createdAt: i.date(),}),},links: {$usersLinkedPrimaryUser: {forward: {on: '$users',has: 'one',label: 'linkedPrimaryUser',onDelete: 'cascade',},reverse: {on: '$users',has: 'many',label: 'linkedGuestUsers',},},},rooms: {},});//...
Push the schema:
npx instant-cli push
Replace the content of src/routes/index.tsx with the following:
import { createFileRoute } from "@tanstack/react-router";import { AppSchema } from "../instant.schema";import { id, InstaQLEntity } from "@instantdb/react";import { db } from "@/lib/db";export const Route = createFileRoute("/")({component: App,});type Todo = InstaQLEntity<AppSchema, "todos">;function App() {// Read Dataconst { isLoading, error, data } = db.useQuery({ todos: {} });if (isLoading) {return null;}if (error) {return <div className="text-red-500 p-4">Error: {error.message}</div>;}const { todos } = data;return (<div className="p-8 max-w-2xl"><div className="bg-white rounded-lg p-6 border border-neutral-200 shadow flex flex-col"><h2 className="tracking-wide text-[#F54A00] pb-2 text-2xl">Todos</h2><div className="text-xs pb-4">Open another tab to see todos update in realtime!</div><div className="border rounded border-neutral-300"><TodoForm /><TodoList todos={todos} /><ActionBar todos={todos} /></div></div></div>);}// Write Data// ---------function addTodo(text: string) {db.transact(db.tx.todos[id()].update({text,done: false,createdAt: Date.now(),}),);}function deleteTodo(todo: Todo) {db.transact(db.tx.todos[todo.id].delete());}function toggleDone(todo: Todo) {db.transact(db.tx.todos[todo.id].update({ done: !todo.done }));}function deleteCompleted(todos: Todo[]) {const completed = todos.filter((todo) => todo.done);if (completed.length === 0) return;const txs = completed.map((todo) => db.tx.todos[todo.id].delete());db.transact(txs);}function TodoForm() {return (<div className="flex items-center h-10 border-neutral-300"><formclassName="flex-1 h-full"onSubmit={(e) => {e.preventDefault();const input = e.currentTarget.input as HTMLInputElement;addTodo(input.value);input.value = "";}}><inputclassName="w-full h-full px-2 outline-none bg-transparent"autoFocusplaceholder="What needs to be done?"type="text"name="input"/></form></div>);}function TodoList({ todos }: { todos: Todo[] }) {return (<div className="divide-y divide-neutral-300">{todos.map((todo) => (<div key={todo.id} className="flex items-center h-10"><div className="h-full px-2 flex items-center justify-center"><div className="w-5 h-5 flex items-center justify-center"><inputtype="checkbox"className="cursor-pointer"checked={todo.done}onChange={() => toggleDone(todo)}/></div></div><div className="flex-1 px-2 overflow-hidden flex items-center">{todo.done ? (<span className="line-through">{todo.text}</span>) : (<span>{todo.text}</span>)}</div><buttonclassName="h-full px-2 flex items-center justify-center text-neutral-300 hover:text-neutral-500"onClick={() => deleteTodo(todo)}>X</button></div>))}</div>);}function ActionBar({ todos }: { todos: Todo[] }) {return (<div className="flex justify-between items-center h-10 px-2 text-xs border-t border-neutral-300"><div>Remaining todos: {todos.filter((todo) => !todo.done).length}</div><buttonclassName=" text-neutral-300 hover:text-neutral-500"onClick={() => deleteCompleted(todos)}>Delete Completed</button></div>);}
Go to localhost:3000, and huzzah 🎉 You've got a fully functional todo list running!
#Next Steps
Want to dive deeper on how this todo app works? Check out our step-by-step Todo List Tutorial. In this tutorial we walk through how to build a todo list app from scratch, and explain how the queries and transactions work in more detail.
For the advanced use case of integrating with TanStack Query and enabling SSR, refer to our tanstack-start-with-tanstack-query example. It can be scaffolded using npx create-instant-app -b tanstack-start-with-tanstack-query.
You can also check out the Working with data section to learn more Instant concepts.
As you get more familiar with Instant, check out our Recommended Workflow docs for using Instant in your projects.