├── .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 | 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 |
17 |
18 |
19 | 22 | 23 |
24 |
25 | 28 | 29 |
30 |
31 | 32 |
33 |
34 |
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 |
17 |
18 |
19 | 22 | 23 |
24 |
25 | 28 | 29 |
30 |
31 | 34 | 35 |
36 |
37 | 40 | 45 |
46 |
47 | 48 |
49 |
50 |
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 | --------------------------------------------------------------------------------