|
1 | 1 | --- |
2 | | -title: TypeScript Quickstart |
3 | | -slug: /sdks/typescript/quickstart |
| 2 | +title: TypeScript |
| 3 | +slug: /quickstarts/typescript |
| 4 | +id: typescript |
4 | 5 | --- |
5 | 6 |
|
6 | | -# TypeScript Client SDK Quickstart |
| 7 | +import { InstallCardLink } from "@site/src/components/InstallCardLink"; |
7 | 8 |
|
8 | | -In this guide, you'll learn how to use TypeScript to create a SpacetimeDB client application. |
| 9 | +# Quickstart Chat App |
9 | 10 |
|
10 | | -Please note that TypeScript is supported as a client language only. **Before you get started on this guide**, you should complete one of the quickstart guides for creating a SpacetimeDB server module listed below. |
| 11 | +In this tutorial, we'll implement a simple chat server as a SpacetimeDB **TypeScript** module. |
11 | 12 |
|
12 | | -- [Rust](/modules/rust/quickstart) |
13 | | -- [C#](/modules/c-sharp/quickstart) |
| 13 | +A SpacetimeDB module is code that gets bundled to a single JavaScript artifact and uploaded to SpacetimeDB. This code becomes server-side logic that interfaces directly with SpacetimeDB’s relational database. |
14 | 14 |
|
15 | | -By the end of this introduction, you will have created a basic single page web app which connects to the `quickstart-chat` database created in the above module quickstart guides. |
| 15 | +Each SpacetimeDB module defines a set of **tables** and a set of **reducers**. |
| 16 | + |
| 17 | +- Tables are declared with `table({ ...opts }, { ...columns })`. Each inserted object is a row; each field is a column. |
| 18 | +- Tables are **private** by default (readable only by the owner and your module code). Set `{ public: true }` to make them readable by everyone; writes still happen only via reducers. |
| 19 | +- A **reducer** is a function that reads/writes the database. Each reducer runs in its own transaction; its writes commit only if it completes without throwing. In TypeScript, reducers are registered with `spacetimedb.reducer(name, argTypes, handler)` and throw `new SenderError("...")` for user-visible errors. |
| 20 | + |
| 21 | +:::note |
| 22 | +SpacetimeDB runs your module inside the database host (not Node.js). There’s no direct filesystem or network access from reducers. |
| 23 | +::: |
| 24 | + |
| 25 | +## Install SpacetimeDB |
| 26 | + |
| 27 | +If you haven’t already, start by [installing SpacetimeDB](pathname:///install). This installs the `spacetime` CLI used to build, publish, and interact with your database. |
| 28 | + |
| 29 | +<InstallCardLink /> |
| 30 | + |
| 31 | +## Project structure |
| 32 | + |
| 33 | +Let's start by running `spacetime init` to initialize our project's directory structure: |
| 34 | + |
| 35 | +```bash |
| 36 | +spacetime init --lang typescript quickstart-chat |
| 37 | +``` |
| 38 | + |
| 39 | +`spacetime init` will ask you for a project path in which to put your project. By default this will be `./quickstart-chat`. This basic project will have a few helper files like Cursor rules for SpacetimeDB and a `spacetimedb` directory which is where your SpacetimeDB module code will go. |
| 40 | + |
| 41 | +Inside the `spacetimedb/` directory will be a `src/index.ts` entrypoint (required for publishing). |
| 42 | + |
| 43 | +## Declare imports |
| 44 | + |
| 45 | +Open `spacetimedb/src/index.ts`. Replace its contents with the following imports to start building a bare-bones real-time chat server: |
| 46 | + |
| 47 | +```ts server |
| 48 | +import { schema, t, table, SenderError } from 'spacetimedb/server'; |
| 49 | +``` |
| 50 | + |
| 51 | +From `spacetimedb/server`, we import: |
| 52 | + |
| 53 | +- `table` to define SpacetimeDB tables. |
| 54 | +- `t` for column/type builders. |
| 55 | +- `schema` to compose our database schema and register reducers. |
| 56 | +- `SenderError` to signal user-visible (transaction-aborting) errors. |
| 57 | + |
| 58 | +## Define tables |
| 59 | + |
| 60 | +We’ll store two kinds of data: information about each user, and the messages that have been sent. |
| 61 | + |
| 62 | +For each `User`, we’ll store their `identity` (the caller’s unique identifier), an optional display `name`, and whether they’re currently `online`. We’ll use `identity` as the primary key (unique and indexed). |
| 63 | + |
| 64 | +Add to `spacetimedb/src/index.ts`: |
| 65 | + |
| 66 | +```ts server |
| 67 | +const User = table( |
| 68 | + { name: 'user', public: true }, |
| 69 | + { |
| 70 | + identity: t.identity().primaryKey(), |
| 71 | + name: t.string().optional(), |
| 72 | + online: t.bool(), |
| 73 | + } |
| 74 | +); |
| 75 | + |
| 76 | +const Message = table( |
| 77 | + { name: 'message', public: true }, |
| 78 | + { |
| 79 | + sender: t.identity(), |
| 80 | + sent: t.timestamp(), |
| 81 | + text: t.string(), |
| 82 | + } |
| 83 | +); |
| 84 | + |
| 85 | +// Compose the schema (gives us ctx.db.user and ctx.db.message, etc.) |
| 86 | +const spacetimedb = schema(User, Message); |
| 87 | +``` |
| 88 | + |
| 89 | +## Set users’ names |
| 90 | + |
| 91 | +We’ll allow users to set a display name, since raw identities aren’t user-friendly. Define a reducer `set_name` that validates input, looks up the caller’s `User` row by primary key, and updates it. If there’s no user row (e.g., the caller invoked via CLI without a connection and hasn’t connected before), we’ll return an error. |
| 92 | + |
| 93 | +Add: |
| 94 | + |
| 95 | +```ts server |
| 96 | +function validateName(name: string) { |
| 97 | + if (!name) { |
| 98 | + throw new SenderError('Names must not be empty'); |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +spacetimedb.reducer('set_name', { name: t.string() }, (ctx, { name }) => { |
| 103 | + validateName(name); |
| 104 | + const user = ctx.db.user.identity.find(ctx.sender); |
| 105 | + if (!user) { |
| 106 | + throw new SenderError('Cannot set name for unknown user'); |
| 107 | + } |
| 108 | + ctx.db.user.identity.update({ ...user, name }); |
| 109 | +}); |
| 110 | +``` |
| 111 | + |
| 112 | +You can extend `validateName` with moderation checks, Unicode normalization, printable-character filtering, max length checks, or duplicate-name rejection. |
| 113 | + |
| 114 | +## Send messages |
| 115 | + |
| 116 | +Define a reducer `send_message` to insert a new `Message` with the caller’s identity and the call timestamp. As with names, we’ll validate that text isn’t empty. |
| 117 | + |
| 118 | +Add: |
| 119 | + |
| 120 | +```ts server |
| 121 | +function validateMessage(text: string) { |
| 122 | + if (!text) { |
| 123 | + throw new SenderError('Messages must not be empty'); |
| 124 | + } |
| 125 | +} |
| 126 | + |
| 127 | +spacetimedb.reducer('send_message', { text: t.string() }, (ctx, { text }) => { |
| 128 | + validateMessage(text); |
| 129 | + console.info(`User ${ctx.sender}: ${text}`); |
| 130 | + ctx.db.message.insert({ |
| 131 | + sender: ctx.sender, |
| 132 | + text, |
| 133 | + sent: ctx.timestamp, |
| 134 | + }); |
| 135 | +}); |
| 136 | +``` |
| 137 | + |
| 138 | +Possible extensions: |
| 139 | + |
| 140 | +- Reject messages from users who haven’t set a name. |
| 141 | +- Rate-limit messages per user. |
| 142 | + |
| 143 | +## Set users’ online status |
| 144 | + |
| 145 | +SpacetimeDB can invoke lifecycle reducers when clients connect/disconnect. We’ll create or update a `User` row to mark the caller online on connect, and mark them offline on disconnect. |
| 146 | + |
| 147 | +Add: |
| 148 | + |
| 149 | +```ts server |
| 150 | +// Called once when the module bundle is installed / updated. |
| 151 | +// We'll keep it empty for this quickstart. |
| 152 | +spacetimedb.init(_ctx => {}); |
| 153 | + |
| 154 | +spacetimedb.clientConnected(ctx => { |
| 155 | + const user = ctx.db.user.identity.find(ctx.sender); |
| 156 | + if (user) { |
| 157 | + // Returning user: set online=true, keep identity/name. |
| 158 | + ctx.db.user.identity.update({ ...user, online: true }); |
| 159 | + } else { |
| 160 | + // New user: create a User row with no name yet. |
| 161 | + ctx.db.user.insert({ |
| 162 | + identity: ctx.sender, |
| 163 | + name: undefined, |
| 164 | + online: true, |
| 165 | + }); |
| 166 | + } |
| 167 | +}); |
| 168 | + |
| 169 | +spacetimedb.clientDisconnected(ctx => { |
| 170 | + const user = ctx.db.user.identity.find(ctx.sender); |
| 171 | + if (user) { |
| 172 | + ctx.db.user.identity.update({ ...user, online: false }); |
| 173 | + } else { |
| 174 | + // Shouldn't happen (disconnect without prior connect) |
| 175 | + console.warn( |
| 176 | + `Disconnect event for unknown user with identity ${ctx.sender}` |
| 177 | + ); |
| 178 | + } |
| 179 | +}); |
| 180 | +``` |
| 181 | + |
| 182 | +## Start the server |
| 183 | + |
| 184 | +If you haven’t already started the SpacetimeDB host on your machine, run this in a **separate terminal** and leave it running: |
| 185 | + |
| 186 | +```bash |
| 187 | +spacetime start |
| 188 | +``` |
| 189 | + |
| 190 | +(If it’s already running, you can skip this step.) |
| 191 | + |
| 192 | +## Publish the module |
| 193 | + |
| 194 | +From the `quickstart-chat` directory (the parent of `spacetimedb/`): |
| 195 | + |
| 196 | +```bash |
| 197 | +spacetime publish --server local --project-path spacetimedb quickstart-chat |
| 198 | +``` |
| 199 | + |
| 200 | +You can choose any unique, URL-safe database name in place of `quickstart-chat`. The CLI will show the database **Identity** (a hex string) as well; you can use either the name or identity with CLI commands. |
| 201 | + |
| 202 | +## Call reducers |
| 203 | + |
| 204 | +Use the CLI to call reducers. Arguments are passed as JSON (strings may be given bare for single string parameters). |
| 205 | + |
| 206 | +Send a message: |
| 207 | + |
| 208 | +```bash |
| 209 | +spacetime call --server local quickstart-chat send_message "Hello, World!" |
| 210 | +``` |
| 211 | + |
| 212 | +Check that it ran by viewing logs (owner-only): |
| 213 | + |
| 214 | +```bash |
| 215 | +spacetime logs --server local quickstart-chat |
| 216 | +``` |
| 217 | + |
| 218 | +You should see output similar to: |
| 219 | + |
| 220 | +```text |
| 221 | +<timestamp> INFO: spacetimedb: Creating table `message` |
| 222 | +<timestamp> INFO: spacetimedb: Creating table `user` |
| 223 | +<timestamp> INFO: spacetimedb: Database initialized |
| 224 | +<timestamp> INFO: console: User 0x...: Hello, World! |
| 225 | +``` |
| 226 | + |
| 227 | +## SQL queries |
| 228 | + |
| 229 | +SpacetimeDB supports a subset of SQL so you can query your data: |
| 230 | + |
| 231 | +```bash |
| 232 | +spacetime sql --server local quickstart-chat "SELECT * FROM message" |
| 233 | +``` |
| 234 | + |
| 235 | +Output will resemble: |
| 236 | + |
| 237 | +```text |
| 238 | + sender | sent | text |
| 239 | +--------------------------------------------------------------------+----------------------------------+----------------- |
| 240 | + 0x93dda09db9a56d8fa6c024d843e805d8262191db3b4ba84c5efcd1ad451fed4e | 2025-04-08T15:47:46.935402+00:00 | "Hello, World!" |
| 241 | +``` |
| 242 | + |
| 243 | +You've just set up your first TypeScript module in SpacetimeDB! You can find the full code for this client [TypeScript server module example](https://github.com/clockworklabs/SpacetimeDB/tree/master/modules/quickstart-chat-ts). |
| 244 | + |
| 245 | +# Creating the client |
| 246 | + |
| 247 | +Next, you'll learn how to use TypeScript to create a SpacetimeDB client application. |
| 248 | + |
| 249 | +By the end of this introduction, you will have created a basic single page web app which connects to the `quickstart-chat` database you just created. |
16 | 250 |
|
17 | 251 | ## Project structure |
18 | 252 |
|
19 | | -Enter the directory `quickstart-chat` you created in the [Rust Module Quickstart](/modules/rust/quickstart) or [C# Module Quickstart](/modules/c-sharp/quickstart) guides: |
| 253 | +Enter the directory `quickstart-chat` you created in the [Rust Module Quickstart](/docs/quickstarts/rust) or [C# Module Quickstart](/docs/quickstarts/c-sharp) guides: |
20 | 254 |
|
21 | 255 | ```bash |
22 | 256 | cd quickstart-chat |
@@ -467,7 +701,7 @@ spacetime generate --lang typescript --out-dir client/src/module_bindings --proj |
467 | 701 |
|
468 | 702 | :::note |
469 | 703 |
|
470 | | -This command assumes you've already created a server module in `quickstart-chat/server`. If you haven't completed one of the server module quickstart guides, you can follow either the [Rust](/modules/rust/quickstart) or [C#](/modules/c-sharp/quickstart) module quickstart to create one and then return here. |
| 704 | +This command assumes you've already created a server module in `quickstart-chat/server`. If you haven't completed one of the server module quickstart guides, you can follow either the [Rust](/docs/quickstarts/rust) or [C#](/docs/quickstarts/c-sharp) module quickstart to create one and then return here. |
471 | 705 |
|
472 | 706 | ::: |
473 | 707 |
|
|
0 commit comments