├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── apps
├── pocketbase
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── fly.toml
│ ├── package.json
│ ├── pb_data
│ │ └── .gitignore
│ └── pb_migrations
│ │ ├── 1722064611_updated_users.js
│ │ ├── 1722310396_created_posts.js
│ │ ├── 1722316694_updated_users.js
│ │ └── README.md
└── web
│ ├── .env.example
│ ├── .eslintignore
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── .prettierignore
│ ├── .prettierrc
│ ├── package.json
│ ├── playwright.config.ts
│ ├── postcss.config.js
│ ├── src
│ ├── app.css
│ ├── app.d.ts
│ ├── app.html
│ ├── index.test.ts
│ ├── lib
│ │ ├── components
│ │ │ ├── MenuListItems.svelte
│ │ │ ├── Post.svelte
│ │ │ └── SignOutButton.svelte
│ │ ├── db
│ │ │ ├── create-post.js
│ │ │ ├── delete-post.js
│ │ │ ├── get-posts.js
│ │ │ └── update-post.js
│ │ ├── icons
│ │ │ ├── Check.svelte
│ │ │ └── Trash.svelte
│ │ ├── index.ts
│ │ ├── pocketbase.js
│ │ └── server
│ │ │ └── pocketbase.js
│ ├── routes
│ │ ├── +layout.svelte
│ │ ├── +page.svelte
│ │ ├── api
│ │ │ ├── auth-token-required
│ │ │ │ └── +server.js
│ │ │ └── create-account
│ │ │ │ └── +server.js
│ │ ├── create-account
│ │ │ └── +page.svelte
│ │ ├── home
│ │ │ ├── +layout.svelte
│ │ │ ├── +page.svelte
│ │ │ └── crud-example
│ │ │ │ └── +page.svelte
│ │ ├── reset-password
│ │ │ └── +page.svelte
│ │ └── sign-in
│ │ │ └── +page.svelte
│ └── types
│ │ ├── api.js
│ │ ├── app.js
│ │ └── db.js
│ ├── static
│ └── favicon.png
│ ├── svelte.config.js
│ ├── tailwind.config.js
│ ├── tests
│ └── test.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── docker-compose.yml
├── package-lock.json
├── package.json
├── packages
└── config-eslint
│ ├── index.js
│ └── package.json
└── turbo.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 | .pnp
6 | .pnp.js
7 |
8 | # testing
9 | coverage
10 |
11 | # svelte
12 | .svelte-kit
13 |
14 | # misc
15 | .DS_Store
16 | *.pem
17 |
18 | # debug
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
23 | # local env files
24 | .env.local
25 | .env.development.local
26 | .env.test.local
27 | .env.production.local
28 |
29 | # turbo
30 | .turbo
31 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | auto-install-peers = true
2 | engine-strict=true
3 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .svelte-kit
3 | node_modules
4 | /build
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 | vite.config.js*
10 |
11 | # Ignore files for PNPM, NPM and YARN
12 | pnpm-lock.yaml
13 | pnpm-workspace.yaml
14 | package-lock.json
15 | yarn.lock
16 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "none",
4 | "printWidth": 100,
5 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
6 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
7 | }
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 arrowban
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SvelteKit, Pocketbase, Turborepo Template
2 |
3 | A template for an extensible SvelteKit + Pocketbase + Turborepo codebase.
4 |
5 | Comes with...
6 |
7 | - **TailwindCSS and DaisyUI:** [Customize the theme](https://daisyui.com/docs/themes) and use [semantic components class names](https://daisyui.com/components)
8 | - **Pre-built account creation, sign in, and reset password flows**
9 | - **Basic CRUD example**
10 | - **Deployment configuration for Pocketbase on [Fly.io](https://fly.io)**
11 |
12 | See the live demo [here](https://sveltekit-pocketbase-turborepo-template.pages.dev), deployed directly from the `demo` branch of this repository.
13 |
14 | _Note: The SvelteKit app supports TypeScript with no additional configuration._
15 |
16 | ### Sections
17 |
18 | - [Screenshots](#screenshots)
19 | - [Requirements](#requirements)
20 | - [Getting started](#getting-started)
21 | - [Set up the development environment](#set-up-the-development-environment)
22 | - [Production deployments](#production-deployments)
23 | - [Deploy the backend](#deploy-the-backend)
24 | - [Deploy on Fly.io](#deploy-on-flyio)
25 | - [Deploy the web app](#deploy-the-web-app)
26 | - [The code](#the-code)
27 | - [Apps and Packages](#apps-and-packages)
28 | - [Utilities](#utilities)
29 |
30 | ### Screenshots
31 |
32 | 
33 | 
34 | 
35 | 
36 | 
37 | 
38 | 
39 | 
40 |
41 | ## Requirements
42 |
43 | [Node.js](https://nodejs.org), and [Docker Desktop](https://www.docker.com/products/docker-desktop) ([Docker](https://www.docker.com) + [Docker Compose](https://docs.docker.com/compose)). Make sure docker is running before getting started.
44 |
45 | ## Getting started
46 |
47 | Create a new repository with the "use this template" button on GitHub, or run the following commands:
48 |
49 | ```sh
50 | git clone --depth=1 --branch=main https://github.com/arrowban/sveltekit-pocketbase-turborepo-template.git my-app
51 |
52 | cd my-app
53 | rm -rf .git
54 |
55 | git init
56 | git add -A
57 | git commit -m "Initial commit"
58 | ```
59 |
60 | ### Set up the development environment
61 |
62 | 1. Install dependencies:
63 |
64 | ```sh
65 | npm install
66 | ```
67 |
68 | 2. Set up environment variables:
69 |
70 | ```sh
71 | cp apps/web/.env.example apps/web/.env.local
72 | ```
73 |
74 | 3. Start the development server and backend
75 |
76 | ```sh
77 | npm run dev
78 | ```
79 |
80 | 4. Go to [the local Pocketbase admin settings page](http://localhost:8090/_) to create an admin account for the local backend
81 | 5. Update environment variables in `apps/web/.env.local` with your admin credentials:
82 |
83 | ```
84 | POCKETBASE_ADMIN_EMAIL=youremail@example.com
85 | POCKETBASE_ADMIN_PASSWORD=your-strong-password
86 | PUBLIC_POCKETBASE_BASE_URL=http://localhost:8090
87 | ```
88 |
89 | 6. [Create an account](http://localhost:5173/create-account) on the local web app, and [start building](http://localhost:5173/home)
90 |
91 | ## Production deployments
92 |
93 | Things to consider for production deployments:
94 |
95 | 1. [Add SMTP server settings](https://pocketbase.io/docs/going-to-production/#use-smtp-mail-server) for sending verification and reset password emails
96 | - Consider turning on the "Forbid authentication for unverified users" option for the users table
97 | - Remember to edit the email templates from the Pocketbase admin settings after deploying
98 | 2. [Integrate OAuth2 providers](https://pocketbase.io/docs/authentication/#oauth2-integration)
99 |
100 | ### Deploy the backend
101 |
102 | It is straightforward to [host Pocketbase](https://pocketbase.io/docs/going-to-production/#deployment-strategies) on any VPS. This template comes configured for easy deployment of Pocketbase on [Fly.io](https://fly.io).
103 |
104 | #### Deploy on Fly.io
105 |
106 | 1. [Install flyctl](https://fly.io/docs/flyctl/install) – the open-source Fly.io CLI
107 | 2. Create an account with `fly auth signup` or log in with `fly auth login`
108 | 3. Create a new app
109 |
110 | ```sh
111 | fly apps create --generate-name
112 | ```
113 |
114 | 4. Add the generated app name to `apps/pocketbase/fly.toml` (line 1)
115 | 5. [Choose the region](https://fly.io/docs/reference/regions) closest to you (or your users) and add the corresponding region ID as the `primary_region` in `apps/pocketbase/fly.toml` (line 2)
116 | 6. Create a new volume, using the same region as the one you chose in step 5 (size can easily be [extended anytime](https://fly.io/docs/volumes/volume-manage/#extend-a-volume))
117 |
118 | ```sh
119 | fly volumes create pb_data --size=1 # Creates a volume with 1GB of storage
120 | ```
121 |
122 | 7. Deploy the Pocketbase server, and run this command again anytime you want to update the deployment after making changes locally
123 |
124 | ```sh
125 | npm run deploy
126 | ```
127 |
128 | 8. Go to the production Pocketbase admin settings page at `https://APP_NAME.fly.dev/_` to create an admin account for the production backend
129 | 9. Add production environment variables to a new file `apps/web/.env.production` with your production admin credentials:
130 |
131 | ```
132 | POCKETBASE_ADMIN_EMAIL=youremail@example.com
133 | POCKETBASE_ADMIN_PASSWORD=your-strong-password
134 | PUBLIC_POCKETBASE_BASE_URL=https://APP_NAME.fly.dev
135 | ```
136 |
137 | The pre-configured VM is the cheapest available on Fly.io (`shared-cpu-1x` with 256MB of memory), [scale the backend vertically](https://fly.io/docs/launch/scale-machine) as your app grows.
138 |
139 | ### Deploy the web app
140 |
141 | Many cloud platforms provide generous free tiers for deploying web apps built with popular frameworks like SvelteKit. Cloudflare, Vercel, and Netlify are just a few examples of platforms that integrate directly with GitHub repositories and provide seamless CI/CD.
142 |
143 | When setting up your first deployment on any of these platforms, remember to:
144 |
145 | - Replace `@sveltejs/adapter-auto` with the [appropriate adapter](https://kit.svelte.dev/docs/adapters) in `apps/web/svelte.config.js`
146 | - Specify `apps/web` as the root of the web app
147 | - Add environment variables from `apps/web/.env.production` to the deployment
148 |
149 | **I strongly recommend deploying the web app with [Cloudflare pages](https://pages.cloudflare.com).**
150 |
151 | ## The code
152 |
153 | ### Apps and Packages
154 |
155 | - `apps/pocketbase`: Dockerized [Pocketbase](https://pocketbase.io) backend
156 | - `apps/web`: [SvelteKit](https://kit.svelte.dev) app
157 | - `packages/eslint-config-custom`: `eslint` configurations
158 |
159 | ### Utilities
160 |
161 | This template has some additional tools already setup for you:
162 |
163 | - [TypeScript](https://www.typescriptlang.org) for static type checking
164 | - [ESLint](https://eslint.org) for code linting
165 | - [Prettier](https://prettier.io) for code formatting
166 |
--------------------------------------------------------------------------------
/apps/pocketbase/.dockerignore:
--------------------------------------------------------------------------------
1 | # flyctl launch added from pb_data/.gitignore
2 | pb_data/**/*
3 | !pb_data/**/.gitignore
4 | fly.toml
5 |
--------------------------------------------------------------------------------
/apps/pocketbase/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:3 as downloader
2 |
3 | ARG TARGETOS
4 | ARG TARGETARCH
5 | ARG TARGETVARIANT
6 | ARG VERSION
7 |
8 | ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}${TARGETVARIANT}"
9 |
10 | RUN wget https://github.com/pocketbase/pocketbase/releases/download/v${VERSION}/pocketbase_${VERSION}_${BUILDX_ARCH}.zip \
11 | && unzip pocketbase_${VERSION}_${BUILDX_ARCH}.zip \
12 | && chmod +x /pocketbase
13 |
14 | FROM alpine:3
15 | RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
16 |
17 | COPY ./pb_migrations /pb_migrations
18 |
19 | EXPOSE 8090
20 |
21 | COPY --from=downloader /pocketbase /usr/local/bin/pocketbase
22 | ENTRYPOINT ["/usr/local/bin/pocketbase", "serve", "--http=0.0.0.0:8090", "--dir=/pb_data", "--migrationsDir=/pb_migrations", "--publicDir=/pb_public", "--hooksDir=/pb_hooks"]
23 |
--------------------------------------------------------------------------------
/apps/pocketbase/fly.toml:
--------------------------------------------------------------------------------
1 | app = "" # Add app name generated by `fly apps create --generate-name`
2 | primary_region = "" # Add a region ID from https://fly.io/docs/reference/regions
3 |
4 | [mounts]
5 | destination = "/pb_data"
6 | source = "pb_data" # Create volume with `fly volumes create pb_data --size=1`
7 |
8 | [build.args]
9 | VERSION = "0.22.18"
10 |
11 | [http_service]
12 | internal_port = 8090
13 | force_https = true
14 | auto_stop_machines = "stop"
15 | auto_start_machines = true
16 | min_machines_running = 0
17 | processes = ["app"]
18 |
19 | [checks]
20 | [checks.health]
21 | grace_period = "30s"
22 | interval = "15s"
23 | method = "get"
24 | path = "/api/health"
25 | port = 8090
26 | timeout = "10s"
27 | type = "http"
28 |
29 | [[vm]]
30 | size = "shared-cpu-1x"
31 | memory = 256
32 |
--------------------------------------------------------------------------------
/apps/pocketbase/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pb",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "dev": "docker compose up",
7 | "deploy": "fly deploy"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/pocketbase/pb_data/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/apps/pocketbase/pb_migrations/1722064611_updated_users.js:
--------------------------------------------------------------------------------
1 | ///
2 | migrate(
3 | (db) => {
4 | const dao = new Dao(db);
5 | const collection = dao.findCollectionByNameOrId('_pb_users_auth_');
6 |
7 | collection.createRule = null;
8 |
9 | return dao.saveCollection(collection);
10 | },
11 | (db) => {
12 | const dao = new Dao(db);
13 | const collection = dao.findCollectionByNameOrId('_pb_users_auth_');
14 |
15 | collection.createRule = '';
16 |
17 | return dao.saveCollection(collection);
18 | }
19 | );
20 |
--------------------------------------------------------------------------------
/apps/pocketbase/pb_migrations/1722310396_created_posts.js:
--------------------------------------------------------------------------------
1 | ///
2 | migrate(
3 | (db) => {
4 | const collection = new Collection({
5 | id: 'eqee25l2eod5yc9',
6 | created: '2024-07-30 03:33:16.509Z',
7 | updated: '2024-07-30 03:33:16.509Z',
8 | name: 'posts',
9 | type: 'base',
10 | system: false,
11 | schema: [
12 | {
13 | system: false,
14 | id: 'typah2jc',
15 | name: 'user',
16 | type: 'relation',
17 | required: true,
18 | presentable: false,
19 | unique: false,
20 | options: {
21 | collectionId: '_pb_users_auth_',
22 | cascadeDelete: false,
23 | minSelect: null,
24 | maxSelect: 1,
25 | displayFields: null
26 | }
27 | },
28 | {
29 | system: false,
30 | id: '43edmncd',
31 | name: 'count',
32 | type: 'number',
33 | required: false,
34 | presentable: false,
35 | unique: false,
36 | options: {
37 | min: null,
38 | max: null,
39 | noDecimal: false
40 | }
41 | }
42 | ],
43 | indexes: [],
44 | listRule: '@request.auth.id != ""',
45 | viewRule: '@request.auth.id != ""',
46 | createRule: 'user = @request.auth.id',
47 | updateRule: 'user = @request.auth.id',
48 | deleteRule: 'user = @request.auth.id',
49 | options: {}
50 | });
51 |
52 | return Dao(db).saveCollection(collection);
53 | },
54 | (db) => {
55 | const dao = new Dao(db);
56 | const collection = dao.findCollectionByNameOrId('eqee25l2eod5yc9');
57 |
58 | return dao.deleteCollection(collection);
59 | }
60 | );
61 |
--------------------------------------------------------------------------------
/apps/pocketbase/pb_migrations/1722316694_updated_users.js:
--------------------------------------------------------------------------------
1 | ///
2 | migrate(
3 | (db) => {
4 | const dao = new Dao(db);
5 | const collection = dao.findCollectionByNameOrId('_pb_users_auth_');
6 |
7 | collection.viewRule = '@request.auth.id != ""';
8 |
9 | return dao.saveCollection(collection);
10 | },
11 | (db) => {
12 | const dao = new Dao(db);
13 | const collection = dao.findCollectionByNameOrId('_pb_users_auth_');
14 |
15 | collection.viewRule = 'id = @request.auth.id';
16 |
17 | return dao.saveCollection(collection);
18 | }
19 | );
20 |
--------------------------------------------------------------------------------
/apps/pocketbase/pb_migrations/README.md:
--------------------------------------------------------------------------------
1 | This directory stores database migrations.
2 |
--------------------------------------------------------------------------------
/apps/web/.env.example:
--------------------------------------------------------------------------------
1 | POCKETBASE_ADMIN_EMAIL=email@example.com
2 | POCKETBASE_ADMIN_PASSWORD=example-password
3 | PUBLIC_POCKETBASE_BASE_URL=http://localhost:8090
4 |
--------------------------------------------------------------------------------
/apps/web/.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 |
--------------------------------------------------------------------------------
/apps/web/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@repo/eslint-config/index.js']
3 | };
4 |
--------------------------------------------------------------------------------
/apps/web/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 | vite.config.js.timestamp-*
10 | vite.config.ts.timestamp-*
11 |
--------------------------------------------------------------------------------
/apps/web/.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 | vite.config.*.timestamp-*
15 |
--------------------------------------------------------------------------------
/apps/web/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "printWidth": 100,
6 | "plugins": ["prettier-plugin-svelte"],
7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
8 | }
9 |
--------------------------------------------------------------------------------
/apps/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web",
3 | "version": "0.0.1",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite dev",
8 | "build": "vite build",
9 | "preview": "vite preview",
10 | "test": "npm run test:integration && npm run test:unit",
11 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
12 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
13 | "lint": "eslint .",
14 | "test:integration": "playwright test",
15 | "test:unit": "vitest"
16 | },
17 | "dependencies": {
18 | "pocketbase": "^0.21.3"
19 | },
20 | "devDependencies": {
21 | "@playwright/test": "^1.42.1",
22 | "@repo/eslint-config": "*",
23 | "@sveltejs/adapter-auto": "^3.1.1",
24 | "@sveltejs/kit": "^2.5.2",
25 | "@sveltejs/vite-plugin-svelte": "^3.0.2",
26 | "@typescript-eslint/eslint-plugin": "^7.1.0",
27 | "@typescript-eslint/parser": "^7.1.0",
28 | "autoprefixer": "^10.4.19",
29 | "daisyui": "^4.12.10",
30 | "eslint": "^8.57.0",
31 | "postcss": "^8.4.40",
32 | "prettier": "^3.2.5",
33 | "prettier-plugin-svelte": "^3.2.2",
34 | "prettier-plugin-tailwindcss": "^0.6.5",
35 | "svelte": "^4.2.12",
36 | "svelte-check": "^3.6.6",
37 | "tailwindcss": "^3.4.7",
38 | "tslib": "^2.6.2",
39 | "typescript": "^5.3.3",
40 | "vite": "^5.1.4",
41 | "vitest": "^1.3.1"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/apps/web/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import type { PlaywrightTestConfig } from '@playwright/test';
2 |
3 | const config: PlaywrightTestConfig = {
4 | webServer: {
5 | command: 'npm run build && npm run preview',
6 | port: 4173
7 | },
8 | testDir: 'tests',
9 | testMatch: /(.+\.)?(test|spec)\.[jt]s/
10 | };
11 |
12 | export default config;
13 |
--------------------------------------------------------------------------------
/apps/web/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/apps/web/src/app.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/apps/web/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://kit.svelte.dev/docs/types#app
2 | // for information about these interfaces
3 | declare global {
4 | namespace App {
5 | // interface Error {}
6 | // interface Locals {}
7 | // interface PageData {}
8 | // interface Platform {}
9 | }
10 | }
11 |
12 | export {};
13 |
--------------------------------------------------------------------------------
/apps/web/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/apps/web/src/index.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from 'vitest';
2 |
3 | describe('sum test', () => {
4 | it('adds 1 + 2 to equal 3', () => {
5 | expect(1 + 2).toBe(3);
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/apps/web/src/lib/components/MenuListItems.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 | CRUD example
6 |
7 |
--------------------------------------------------------------------------------
/apps/web/src/lib/components/Post.svelte:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 |
32 |
Post ID: {post.id}
33 |
User ID: {post.user}
34 | {#if post.expand}
35 |
Username: {post.expand.user.username}
36 | {/if}
37 |
Count: {post.count}
38 |
39 |
46 |
49 |
50 |
51 |
Created: {post.created}
52 |
Updated: {post.updated}
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/apps/web/src/lib/components/SignOutButton.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/apps/web/src/lib/db/create-post.js:
--------------------------------------------------------------------------------
1 | import { pb } from '$lib/pocketbase';
2 |
3 | /**
4 | * @param {Pick} data
5 | */
6 | export async function createPost(data) {
7 | try {
8 | /** @type {PostRecordModel} */
9 | const record = await pb.collection('posts').create(data, { expand: 'user' });
10 | return { data: record };
11 | } catch (err) {
12 | return { error: /** @type {Error} */ (err).message };
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/apps/web/src/lib/db/delete-post.js:
--------------------------------------------------------------------------------
1 | import { pb } from '$lib/pocketbase';
2 |
3 | /**
4 | * @param {string} id
5 | */
6 | export async function deletePost(id) {
7 | try {
8 | await pb.collection('posts').delete(id);
9 | return {};
10 | } catch (err) {
11 | return { error: /** @type {Error} */ (err).message };
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/apps/web/src/lib/db/get-posts.js:
--------------------------------------------------------------------------------
1 | import { pb } from '$lib/pocketbase';
2 |
3 | export async function getPosts() {
4 | try {
5 | /** @type {PostRecordModel[]} */
6 | const fullList = await pb.collection('posts').getFullList({ expand: 'user', sort: '-created' });
7 | return { data: fullList };
8 | } catch (err) {
9 | return { error: /** @type {Error} */ (err).message };
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/apps/web/src/lib/db/update-post.js:
--------------------------------------------------------------------------------
1 | import { pb } from '$lib/pocketbase';
2 |
3 | /**
4 | * @param {string} id
5 | * @param {Partial>} data
6 | */
7 | export async function updatePost(id, data) {
8 | try {
9 | /** @type {PostRecordModel} */
10 | const record = await pb.collection('posts').update(id, data, { expand: 'user' });
11 | return { data: record };
12 | } catch (err) {
13 | return { error: /** @type {Error} */ (err).message };
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/apps/web/src/lib/icons/Check.svelte:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/apps/web/src/lib/icons/Trash.svelte:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/apps/web/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | // place files you want to import through the `$lib` alias in this folder.
2 |
--------------------------------------------------------------------------------
/apps/web/src/lib/pocketbase.js:
--------------------------------------------------------------------------------
1 | import { PUBLIC_POCKETBASE_BASE_URL } from '$env/static/public';
2 | import PocketBase from 'pocketbase';
3 |
4 | export const pb = new PocketBase(PUBLIC_POCKETBASE_BASE_URL);
5 |
--------------------------------------------------------------------------------
/apps/web/src/lib/server/pocketbase.js:
--------------------------------------------------------------------------------
1 | import { env } from '$env/dynamic/private';
2 | import { PUBLIC_POCKETBASE_BASE_URL } from '$env/static/public';
3 | import PocketBase from 'pocketbase';
4 |
5 | export function newPocketBase() {
6 | const baseUrl = env.POCKETBASE_BASE_URL || PUBLIC_POCKETBASE_BASE_URL;
7 | return new PocketBase(baseUrl);
8 | }
9 |
--------------------------------------------------------------------------------
/apps/web/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/apps/web/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
SvelteKit, Pocketbase, Turborepo Template
18 |
Don't forget to name this thing.
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/apps/web/src/routes/api/auth-token-required/+server.js:
--------------------------------------------------------------------------------
1 | import { newPocketBase } from '$lib/server/pocketbase';
2 | import { json } from '@sveltejs/kit';
3 |
4 | /** @type {import('./$types').RequestHandler} */
5 | export async function POST({ request }) {
6 | try {
7 | const authHeader = request.headers.get('x-auth-token');
8 | if (!authHeader) {
9 | throw new Error('Unauthorized.');
10 | }
11 |
12 | const body = await request.json();
13 |
14 | const pb = newPocketBase();
15 | pb.authStore.save(authHeader);
16 |
17 | /** @type {UserAuthRefresh} */
18 | const user = await pb.collection('users').authRefresh();
19 |
20 | // Do stuff...
21 |
22 | pb.authStore.clear();
23 |
24 | return json({
25 | data: { body, user }
26 | });
27 | } catch (err) {
28 | return json({
29 | error: /** @type {Error} */ (err).message
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/apps/web/src/routes/api/create-account/+server.js:
--------------------------------------------------------------------------------
1 | import { env } from '$env/dynamic/private';
2 | import { newPocketBase } from '$lib/server/pocketbase';
3 | import { json } from '@sveltejs/kit';
4 |
5 | /** @type {import('./$types').RequestHandler} */
6 | export async function POST({ request }) {
7 | try {
8 | /** @type {ApiCreateAccountParams} */
9 | const body = await request.json();
10 | const { email, password, passwordConfirm } = body;
11 |
12 | const pb = newPocketBase();
13 | await pb.admins.authWithPassword(env.POCKETBASE_ADMIN_EMAIL, env.POCKETBASE_ADMIN_PASSWORD);
14 | const createUserRecordModel = await pb
15 | .collection('users')
16 | .create({ email, password, passwordConfirm });
17 | pb.authStore.clear();
18 |
19 | return json({
20 | data: { createUserRecordModel }
21 | });
22 | } catch (err) {
23 | return json({
24 | error: /** @type {Error} */ (err).message
25 | });
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/apps/web/src/routes/create-account/+page.svelte:
--------------------------------------------------------------------------------
1 |
48 |
49 | SvelteKit, Pocketbase, Turborepo Template
52 |
118 |
--------------------------------------------------------------------------------
/apps/web/src/routes/home/+layout.svelte:
--------------------------------------------------------------------------------
1 |
20 |
21 | {#if $user}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
43 |
44 |
45 |
46 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
61 |
62 |
63 | {/if}
64 |
--------------------------------------------------------------------------------
/apps/web/src/routes/home/+page.svelte:
--------------------------------------------------------------------------------
1 | Your next amazing project 🎉
2 |
--------------------------------------------------------------------------------
/apps/web/src/routes/home/crud-example/+page.svelte:
--------------------------------------------------------------------------------
1 |
108 |
109 |
110 | CRUD example
111 | {#if error}
112 | {error}
113 | {/if}
114 |
115 | {#if data === null}
116 |
117 | {:else}
118 |
119 | {#each data as post, index}
120 | -
121 | onClickDeletePost(index)}
124 | incrementCount={() => onClickIncrementCount(index)}
125 | />
126 |
127 | {/each}
128 |
129 | {/if}
130 |
131 |
--------------------------------------------------------------------------------
/apps/web/src/routes/reset-password/+page.svelte:
--------------------------------------------------------------------------------
1 |
29 |
30 | SvelteKit, Pocketbase, Turborepo Template
33 |
79 |
--------------------------------------------------------------------------------
/apps/web/src/routes/sign-in/+page.svelte:
--------------------------------------------------------------------------------
1 |
27 |
28 | SvelteKit, Pocketbase, Turborepo Template
31 |
87 |
--------------------------------------------------------------------------------
/apps/web/src/types/api.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @typedef {object} ApiCreateAccountParams
3 | * @property {string} email
4 | * @property {string} password
5 | * @property {string} passwordConfirm
6 | */
7 |
--------------------------------------------------------------------------------
/apps/web/src/types/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @typedef {import("svelte/store").Writable} TokenStore
3 | * @typedef {import("svelte/store").Writable} UserStore
4 | */
5 |
--------------------------------------------------------------------------------
/apps/web/src/types/db.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @typedef {import("pocketbase").RecordModel } RecordModel
3 | *
4 | * @typedef {object} User
5 | * @property {string} id
6 | * @property {string} created
7 | * @property {string} updated
8 | * @property {string} username
9 | * @property {string=} email
10 | * @property {boolean} emailVisibility
11 | * @property {boolean} verified
12 | * @property {string} name
13 | * @property {string} avatar
14 | *
15 | * @typedef {RecordModel & User } UserRecordModel
16 | * @typedef {import("pocketbase").RecordAuthResponse } UserAuthRefresh
17 | *
18 | * @typedef {object} Post
19 | * @property {string} id
20 | * @property {string} created
21 | * @property {string} updated
22 | * @property {string} user
23 | * @property {number} count
24 | * @property {{user: User}=} expand
25 | *
26 | * @typedef {RecordModel & Post} PostRecordModel
27 | */
28 |
--------------------------------------------------------------------------------
/apps/web/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arrowban/sveltekit-pocketbase-turborepo-template/0bda7f92418cb2a209867f5c80836c2426013491/apps/web/static/favicon.png
--------------------------------------------------------------------------------
/apps/web/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-auto';
2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | preprocess: vitePreprocess(),
7 | kit: {
8 | adapter: adapter()
9 | }
10 | };
11 |
12 | export default config;
13 |
--------------------------------------------------------------------------------
/apps/web/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ['./src/**/*.{html,js,svelte,ts}'],
4 | theme: {
5 | extend: {}
6 | },
7 | plugins: [require('daisyui')]
8 | };
9 |
--------------------------------------------------------------------------------
/apps/web/tests/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test';
2 |
3 | test('index page has expected h1', async ({ page }) => {
4 | await page.goto('/');
5 | await expect(page.getByRole('heading', { name: 'Web' })).toBeVisible();
6 | });
7 |
--------------------------------------------------------------------------------
/apps/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true
12 | }
13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
14 | //
15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
16 | // from the referenced tsconfig.json - TypeScript does not merge them in
17 | }
18 |
--------------------------------------------------------------------------------
/apps/web/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import { defineConfig } from 'vitest/config';
3 |
4 | export default defineConfig({
5 | plugins: [sveltekit()],
6 | test: {
7 | include: ['src/**/*.{test,spec}.{js,ts}']
8 | },
9 | build: {
10 | commonjsOptions: {
11 | include: [/@repo\/ui/, /node_modules/]
12 | }
13 | }
14 | });
15 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | pocketbase:
3 | build:
4 | context: ./apps/pocketbase
5 | args:
6 | - VERSION=0.22.18
7 | restart: unless-stopped
8 | ports:
9 | - '8090:8090'
10 | volumes:
11 | - ./apps/pocketbase/pb_data:/pb_data
12 | - ./apps/pocketbase/pb_migrations:/pb_migrations
13 | # - /path/to/public:/pb_public
14 | # - /path/to/hooks:/pb_hooks
15 | healthcheck:
16 | test: wget --no-verbose --tries=1 --spider http://localhost:8090/api/health || exit 1
17 | interval: 5s
18 | timeout: 5s
19 | retries: 5
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "turbo run build",
5 | "dev": "turbo run dev",
6 | "check": "turbo run check",
7 | "lint": "turbo run lint",
8 | "format": "prettier --write .",
9 | "deploy": "turbo run deploy"
10 | },
11 | "devDependencies": {
12 | "prettier": "^3.2.5",
13 | "prettier-plugin-svelte": "^3.2.2",
14 | "turbo": "^2.0.9"
15 | },
16 | "engines": {
17 | "node": ">=18"
18 | },
19 | "name": "sveltekit-pocketbase-turborepo-template",
20 | "packageManager": "npm@10.2.5",
21 | "workspaces": [
22 | "apps/*",
23 | "packages/*"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/packages/config-eslint/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: [
4 | 'eslint:recommended',
5 | 'plugin:@typescript-eslint/recommended',
6 | 'plugin:svelte/recommended',
7 | 'prettier',
8 | 'turbo'
9 | ],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['@typescript-eslint'],
12 | parserOptions: {
13 | sourceType: 'module',
14 | ecmaVersion: 2020,
15 | extraFileExtensions: ['.svelte']
16 | },
17 | env: {
18 | browser: true,
19 | es2017: true,
20 | node: true
21 | },
22 | overrides: [
23 | {
24 | files: ['*.svelte'],
25 | parser: 'svelte-eslint-parser',
26 | parserOptions: {
27 | parser: '@typescript-eslint/parser'
28 | }
29 | }
30 | ]
31 | };
32 |
--------------------------------------------------------------------------------
/packages/config-eslint/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/eslint-config",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | "@typescript-eslint/eslint-plugin": "^7.1.0",
6 | "@typescript-eslint/parser": "^7.1.0",
7 | "eslint-config-prettier": "^9.1.0",
8 | "eslint-config-turbo": "^2.0.0",
9 | "eslint-plugin-svelte": "^2.35.1"
10 | },
11 | "publishConfig": {
12 | "access": "public"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "tasks": {
4 | "build": {
5 | "dependsOn": ["^build"],
6 | "inputs": ["$TURBO_DEFAULT", ".env*"],
7 | "outputs": [".svelte-kit/**", ".vercel/**"]
8 | },
9 | "check": {},
10 | "lint": {},
11 | "dev": {
12 | "cache": false,
13 | "persistent": true
14 | },
15 | "deploy": {
16 | "cache": false
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------