├── .eslintignore
├── .eslintrc.cjs
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── README.md
├── package-lock.json
├── package.json
├── playwright.config.js
├── postcss.config.cjs
├── src
├── app.html
├── app.postcss
├── hooks.server.js
├── lib
│ └── helpers.js
└── routes
│ ├── +layout.server.js
│ ├── +layout.svelte
│ ├── +page.svelte
│ ├── login
│ ├── +page.server.js
│ └── +page.svelte
│ ├── logout
│ └── +server.js
│ └── register
│ ├── +page.server.js
│ └── +page.svelte
├── static
└── favicon.png
├── svelte.config.js
├── tailwind.config.cjs
├── tests
└── test.js
└── vite.config.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['eslint:recommended', 'prettier'],
4 | plugins: ['svelte3'],
5 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
6 | parserOptions: {
7 | sourceType: 'module',
8 | ecmaVersion: 2020
9 | },
10 | env: {
11 | browser: true,
12 | es2017: true,
13 | node: true
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "printWidth": 100,
6 | "plugins": ["prettier-plugin-svelte"],
7 | "pluginSearchDirs": ["."],
8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
9 | }
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SvelteKit & PocketBase Auth
2 |
3 | This project was built in a [video](https://youtu.be/doDKaKDvB30) on my YouTube channel and is a basic integration of PocketBase into a SvelteKit application to enable user authentication and basic route protection.
4 |
5 | # Get Started
6 |
7 | ***You will need to have a [PocketBase](https://pocketbase.io) server running on localhost:8090 for this to function out of the box***
8 |
9 | ## Setup Starter Code
10 | ```bash
11 | git clone --single-branch --branch starter git@github.com:huntabyte/sveltekit-pocketbase-auth.git
12 | ```
13 |
14 | ### OR
15 |
16 | ## Final Source Code
17 | Clone this repository
18 | ```bash
19 | git clone git@github.com:huntabyte/sveltekit-pocketbase-auth.git
20 | ```
21 |
22 |
23 |
24 | Install dependencies
25 | ```bash
26 | npm i
27 | ```
28 |
29 | Run Development Server
30 | ```bash
31 | npm run dev
32 | ```
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite dev",
7 | "build": "vite build",
8 | "preview": "vite preview",
9 | "test": "playwright test",
10 | "lint": "prettier --plugin-search-dir . --check . && eslint .",
11 | "format": "prettier --plugin-search-dir . --write ."
12 | },
13 | "devDependencies": {
14 | "@playwright/test": "^1.25.0",
15 | "@sveltejs/adapter-auto": "next",
16 | "@sveltejs/kit": "next",
17 | "autoprefixer": "^10.4.7",
18 | "daisyui": "^2.31.0",
19 | "eslint": "^8.16.0",
20 | "eslint-config-prettier": "^8.3.0",
21 | "eslint-plugin-svelte3": "^4.0.0",
22 | "postcss": "^8.4.14",
23 | "postcss-load-config": "^4.0.1",
24 | "prettier": "^2.6.2",
25 | "prettier-plugin-svelte": "^2.7.0",
26 | "svelte": "^3.44.0",
27 | "svelte-icons": "^2.1.0",
28 | "svelte-preprocess": "^4.10.7",
29 | "tailwindcss": "^3.1.5",
30 | "vite": "^3.1.0"
31 | },
32 | "type": "module",
33 | "dependencies": {
34 | "pocketbase": "^0.7.4"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/playwright.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('@playwright/test').PlaywrightTestConfig} */
2 | const config = {
3 | webServer: {
4 | command: 'npm run build && npm run preview',
5 | port: 4173
6 | }
7 | };
8 |
9 | export default config;
10 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | const tailwindcss = require('tailwindcss');
2 | const autoprefixer = require('autoprefixer');
3 |
4 | const config = {
5 | plugins: [
6 | //Some plugins, like tailwindcss/nesting, need to run before Tailwind,
7 | tailwindcss(),
8 | //But others, like autoprefixer, need to run after,
9 | autoprefixer
10 | ]
11 | };
12 |
13 | module.exports = config;
14 |
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/app.postcss:
--------------------------------------------------------------------------------
1 | /* Write your global styles here, in PostCSS syntax */
2 | @tailwind base;
3 | @tailwind components;
4 | @tailwind utilities;
5 |
--------------------------------------------------------------------------------
/src/hooks.server.js:
--------------------------------------------------------------------------------
1 | import PocketBase from 'pocketbase';
2 |
3 | export const handle = async ({ event, resolve }) => {
4 | event.locals.pb = new PocketBase('http://localhost:8090');
5 | event.locals.pb.authStore.loadFromCookie(event.request.headers.get('cookie') || '');
6 |
7 | if (event.locals.pb.authStore.isValid) {
8 | event.locals.user = event.locals.pb.authStore.model;
9 | }
10 |
11 | const response = await resolve(event);
12 |
13 | // TODO: secure before deployment
14 | response.headers.set('set-cookie', event.locals.pb.authStore.exportToCookie({ secure: false }));
15 |
16 | return response;
17 | };
18 |
--------------------------------------------------------------------------------
/src/lib/helpers.js:
--------------------------------------------------------------------------------
1 | export const serializeNonPOJOs = (obj) => {
2 | return JSON.parse(JSON.stringify(obj));
3 | };
4 |
--------------------------------------------------------------------------------
/src/routes/+layout.server.js:
--------------------------------------------------------------------------------
1 | import { serializeNonPOJOs } from '$lib/helpers';
2 |
3 | export const load = ({ locals }) => {
4 | if (locals.user && locals.user.profile) {
5 | return {
6 | profile: serializeNonPOJOs(locals.user.profile)
7 | };
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
28 |
31 | {#if data?.profile}
32 | -
33 |
36 |
37 | {:else}
38 | - Login
39 | - Register
40 | {/if}
41 |
42 |
43 |
47 |
48 |
49 |
50 |
51 | {#if data?.profile}
52 |
55 | {:else}
56 |
Login
57 |
Register
58 | {/if}
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | Welcome to the SvelteKit & PocketBase Project, {data?.profile?.name || ''}! 👋
7 |
8 |
--------------------------------------------------------------------------------
/src/routes/login/+page.server.js:
--------------------------------------------------------------------------------
1 | import { redirect } from '@sveltejs/kit';
2 |
3 | export const load = ({ locals }) => {
4 | if (locals.pb.authStore.isValid) {
5 | throw redirect(303, '/');
6 | }
7 | };
8 |
9 | export const actions = {
10 | login: async ({ request, locals }) => {
11 | const formData = await request.formData();
12 | const data = Object.fromEntries([...formData]);
13 |
14 | try {
15 | const { token, user } = await locals.pb.users.authViaEmail(data.email, data.password);
16 | } catch (err) {
17 | console.log('Error:', err);
18 | return {
19 | error: true,
20 | email: data.email
21 | };
22 | }
23 | throw redirect(303, '/');
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/src/routes/login/+page.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Sign in to your account
11 |
12 |
13 | Or register if you don't
14 | already have an account.
15 |
16 |
35 |
36 |
--------------------------------------------------------------------------------
/src/routes/logout/+server.js:
--------------------------------------------------------------------------------
1 | import { redirect } from '@sveltejs/kit';
2 |
3 | export const POST = ({ locals }) => {
4 | locals.pb.authStore.clear();
5 | locals.user = undefined;
6 |
7 | throw redirect(303, '/');
8 | };
9 |
--------------------------------------------------------------------------------
/src/routes/register/+page.server.js:
--------------------------------------------------------------------------------
1 | import { redirect } from '@sveltejs/kit';
2 |
3 | export const load = ({ locals }) => {
4 | if (locals.pb.authStore.isValid) {
5 | throw redirect(303, '/');
6 | }
7 | };
8 |
9 | export const actions = {
10 | register: async ({ locals, request }) => {
11 | const formData = await request.formData();
12 | const data = Object.fromEntries([...formData]);
13 |
14 | try {
15 | const newUser = await locals.pb.users.create(data);
16 |
17 | const { token, user } = await locals.pb.users.authViaEmail(data.email, data.password);
18 |
19 | const updatedProfile = await locals.pb.records.update('profiles', user.profile.id, {
20 | name: data.name
21 | });
22 |
23 | locals.pb.authStore.clear();
24 | } catch (err) {
25 | console.log('Error:', err);
26 | return {
27 | error: true,
28 | message: err
29 | };
30 | }
31 |
32 | throw redirect(303, '/login');
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/src/routes/register/+page.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Register for an account
11 |
12 |
13 | Or sign in if you already
14 | have an account.
15 |
16 |
51 |
52 |
--------------------------------------------------------------------------------
/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huntabyte/sveltekit-pocketbase-auth/5b209bae500901725dff01f88966b548f10c3389/static/favicon.png
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | import preprocess from 'svelte-preprocess';
2 | import adapter from '@sveltejs/adapter-auto';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | kit: {
7 | adapter: adapter()
8 | },
9 |
10 | preprocess: [
11 | preprocess({
12 | postcss: true
13 | })
14 | ]
15 | };
16 |
17 | export default config;
18 |
--------------------------------------------------------------------------------
/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | const config = {
2 | content: ['./src/**/*.{html,js,svelte,ts}'],
3 |
4 | theme: {
5 | extend: {}
6 | },
7 |
8 | plugins: [require('daisyui')]
9 | };
10 |
11 | module.exports = config;
12 |
--------------------------------------------------------------------------------
/tests/test.js:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test';
2 |
3 | test('index page has expected h1', async ({ page }) => {
4 | await page.goto('/');
5 | expect(await page.textContent('h1')).toBe('Welcome to SvelteKit');
6 | });
7 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 |
3 | const config = {
4 | plugins: [sveltekit()]
5 | };
6 |
7 | export default config;
8 |
--------------------------------------------------------------------------------