Pocketbase Chat App
In the following tutorial we will build a chat app with Pocketbase and Svelte. Pocketbase is a backend (inspired by Firebase) structured as a single executable file. It provides a realtime DB based on SQLite and user authentication system that allows us to quickly prototype and deploy a chat app.
Pocketbase Setup
Serve it Locally
First, download pocketbase then run the executable locally with ./pocketbase serve
. You should have access to an admin dashboard that looks like this:
Data Model
Our chat app will have two collections: users
and messages
.
The messages
collection will store the messages sent by users. Most importantly, it should have a relation field that points to the users collection.
Frontend App
Create a Svelte App
Create a new svelte app with Vite and select the TypeScript option. Install Pocketbase JS and serve the app.
npm init vite pocketchat
cd pocketchat
npm install
npm install pocketbase
npm run dev
Get the Current User
Put the current user from Pocketbase into a Svelte store.
import PocketBase from 'pocketbase';
import { writable } from 'svelte/store';
const pb = new PocketBase('http://127.0.0.1:8090');
export const currentUser = writable(pb.authStore.model);
pb.authStore.onChange((auth) => {
console.log('authStore changed', auth);
currentUser.set(pb.authStore.model);
});
Sign into the App
Let’s use the username/password authentication strategy.
Create a login form that uses the authWithPassword
method. If the user doesn’t exist, create a new user with the create
method.
<script lang="ts">
import { currentUser, pb } from './pocketbase';
let username: string;
let password: string;
async function login() {
const user = await pb.collection('users').authWithPassword(username, password);
console.log(user)
}
async function signUp() {
try {
const data = {
username,
password,
passwordConfirm: password,
name: 'hi mom!',
};
const createdUser = await pb.collection('users').create(data);
await login();
} catch (err) {
console.error(err)
}
}
function signOut() {
pb.authStore.clear();
}
</script>
{#if $currentUser}
<p>
Signed in as {$currentUser.username}
<button on:click={signOut}>Sign Out</button>
</p>
{:else}
<form on:submit|preventDefault>
<input
placeholder="Username"
type="text"
bind:value={username}
/>
<input
placeholder="Password"
type="password"
bind:value={password}
/>
<button on:click={signUp}>Sign Up</button>
<button on:click={login}>Login</button>
</form>
{/if}
Realtime Chat Messages
- When the component mounts, fetch the latest messages.
- Setup a realtime listener for new messages and react the actions like
create
anddelete
. Make sure to unsubscribe when the component is destroyed. - Create new messages by passing the current user’s ID to the
user
field.
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { currentUser, pb } from './pocketbase';
let newMessage: string;
let messages = [];
let unsubscribe: () => void;
onMount(async () => {
// Get initial messages
const resultList = await pb.collection('messages').getList(1, 50, {
sort: 'created',
expand: 'user',
});
messages = resultList.items;
// Subscribe to realtime messages
unsubscribe = await pb
.collection('messages')
.subscribe('*', async ({ action, record }) => {
if (action === 'create') {
// Fetch associated user
const user = await pb.collection('users').getOne(record.user);
record.expand = { user };
messages = [...messages, record];
}
if (action === 'delete') {
messages = messages.filter((m) => m.id !== record.id);
}
});
});
// Unsubscribe from realtime messages
onDestroy(() => {
unsubscribe?.();
});
async function sendMessage() {
const data = {
text: newMessage,
user: $currentUser.id,
};
const createdMessage = await pb.collection('messages').create(data);
newMessage = '';
}
</script>
<div class="messages">
{#each messages as message (message.id)}
<div class="msg">
<img
class="avatar"
src={`https://avatars.dicebear.com/api/identicon/${message.expand?.user?.username}.svg`}
alt="avatar"
width="40px"
/>
<div>
<small>
Sent by @{message.expand?.user?.username}
</small>
<p class="msg-text">{message.text}</p>
</div>
</div>
{/each}
</div>
<form on:submit|preventDefault={sendMessage}>
<input placeholder="Message" type="text" bind:value={newMessage} />
<button type="submit">Send</button>
</form>
Deploying Pocketbase to Linode
Create a Linode
Head over the Linode and create a new server. Because they sponsored this video, you can use a $100 credit to get started.
Copy Pocketbase to Linode
Copy the pocketbase executable to your server using scp or any other tool of your prefrence.
scp pocketbase root@YOUR-IP:/root/pb
Run Pocketbase with Systemd
You could simply run the pocketbase command in a terminal, but your backend will stop running if the server ever needs to reboot.
A more reliable to run Pocketbase is with a service manager like systemd. You can use the following service file to run Pocketbase automatically on boot
Note: Replace YOUR-DOMAIN
with your domain name or IP address. Remove the --https
flag if you don’t have a domain name.
[Unit]
Description = pocketbase
[Service]
Type = simple
User = root
Group = root
LimitNOFILE = 4096
Restart = always
RestartSec = 5s
StandardOutput = append:/root/pb/errors.log
StandardError = append:/root/pb/errors.log
ExecStart = /root/pb/pocketbase serve --http="YOUR-DOMAIN:80" --https="YOUR-DOMAIN:443"
[Install]
WantedBy = multi-user.target
Create this file on your server, then enable and start the service:
systemctl enable pocketbase.service
systemctl start pocketbase