Nuxt Quickstart
Get a SpacetimeDB Nuxt app running in under 5 minutes.
Prerequisites
- Node.js 18+ installed
- SpacetimeDB CLI installed
Run the spacetime dev command to create a new project with a SpacetimeDB module and Nuxt client.
This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Nuxt development server.
spacetime dev --template nuxt-tsNavigate to http://localhost:5173 to see your app running.
The template includes a basic Nuxt app connected to SpacetimeDB.
Your project contains both server and client code.
Edit spacetimedb/src/index.ts to add tables and reducers. Edit components/AppContent.vue to build your UI, and app.vue to configure the SpacetimeDB connection.
my-spacetime-app/
├── spacetimedb/ # Your SpacetimeDB module
│ └── src/
│ └── index.ts # SpacetimeDB module logic
├── app.vue # Root component with provider
├── components/
│ └── AppContent.vue # Main UI component
├── server/
│ └── api/
│ └── people.get.ts # Server-side data fetching
├── module_bindings/ # Auto-generated types
├── nuxt.config.ts # Nuxt configuration
└── package.json
Open spacetimedb/src/index.ts to see the module code. The template includes a person table and two reducers: add to insert a person, and sayHello to greet everyone.
Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
import { schema, table, t } from 'spacetimedb/server';
const spacetimedb = schema({
person: table(
{ public: true },
{
name: t.string(),
}
),
});
export default spacetimedb;
export const add = spacetimedb.reducer(
{ name: t.string() },
(ctx, { name }) => {
ctx.db.person.insert({ name });
}
);
export const sayHello = spacetimedb.reducer(ctx => {
for (const person of ctx.db.person.iter()) {
console.info(`Hello, ${person.name}!`);
}
console.info('Hello, World!');
});Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly.
cd my-spacetime-app
# Call the add reducer to insert a person
spacetime call add Alice
# Query the person table
spacetime sql "SELECT * FROM person"
name
---------
"Alice"
# Call sayHello to greet everyone
spacetime call say_hello
# View the module logs
spacetime logs
2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
2025-01-13T12:00:00.000000Z INFO: Hello, World!The SpacetimeDB SDK works both server-side and client-side. The template uses a hybrid approach:
- Server API route (
server/api/people.get.ts): Fetches initial data during SSR for fast page loads - Client composables: Maintain a real-time WebSocket connection for live updates
The server API route connects to SpacetimeDB, subscribes, fetches data, and disconnects.
// server/api/people.get.ts
import { DbConnection, tables } from '../../module_bindings';
export default defineEventHandler(async () => {
return new Promise((resolve, reject) => {
DbConnection.builder()
.withUri(process.env.SPACETIMEDB_HOST!)
.withDatabaseName(process.env.SPACETIMEDB_DB_NAME!)
.onConnect((conn) => {
conn.subscriptionBuilder()
.onApplied(() => {
const people = Array.from(conn.db.person.iter());
conn.disconnect();
resolve(people);
})
.subscribe(tables.person);
})
.build();
});
});The root app.vue wraps your app in a SpacetimeDBProvider that manages the WebSocket connection. The provider is wrapped in ClientOnly so it only runs in the browser, while SSR uses the server API route for initial data.
With no token, SpacetimeDB issues a server-issued identity and a non-expiring token; persist it and pass it back on reconnect to keep the same identity. A lost token can't be recovered, so self-issued identities are for development. For production, authenticate with an OIDC provider such as SpacetimeAuth, which handles token lifecycle. See Authentication.
<!-- app.vue -->
<template>
<ClientOnly>
<SpacetimeDBProvider :connection-builder="connectionBuilder">
<AppContent />
</SpacetimeDBProvider>
<template #fallback>
<AppContent />
</template>
</ClientOnly>
</template>
<script setup lang="ts">
import { SpacetimeDBProvider } from 'spacetimedb/vue';
import { DbConnection } from './module_bindings';
const HOST = import.meta.env.VITE_SPACETIMEDB_HOST ?? 'ws://localhost:3000';
const DB_NAME = import.meta.env.VITE_SPACETIMEDB_DB_NAME ?? 'nuxt-ts';
const TOKEN_KEY = `${HOST}/${DB_NAME}/auth_token`;
const connectionBuilder = import.meta.client
? DbConnection.builder()
.withUri(HOST)
.withDatabaseName(DB_NAME)
.withToken(localStorage.getItem(TOKEN_KEY) || undefined)
.onConnect((_conn, identity, token) => {
localStorage.setItem(TOKEN_KEY, token);
console.log('Connected:', identity.toHexString());
})
.onDisconnect(() => console.log('Disconnected'))
.onConnectError((_ctx, err) => console.log('Error:', err))
: undefined;
</script>
Use useFetch to load initial data server-side, then Vue composables for real-time updates on the client. The component displays server-fetched data immediately while the WebSocket connection establishes.
<!-- components/AppContent.vue -->
<script setup lang="ts">
import { ref, computed } from 'vue';
import { tables, reducers } from '../module_bindings';
// Fetch initial data server-side for SSR
const { data: initialPeople } = await useFetch('/api/people');
// On the client, use real-time composables
let conn, people, addReducer;
if (import.meta.client) {
const { useSpacetimeDB, useTable, useReducer } = await import('spacetimedb/vue');
conn = useSpacetimeDB();
[people] = useTable(tables.person);
addReducer = useReducer(reducers.add);
}
// Use real-time data once connected, fall back to SSR data
const displayPeople = computed(() => {
if (conn?.isActive && people?.value) return people.value;
return initialPeople.value ?? [];
});
</script>
Next steps
- Read the TypeScript SDK Reference for detailed API docs