Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Thomas's svelte auth guide #137

Merged
merged 6 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/lib/utils/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { Platform } from './references';

const languages = {
js: javascript,
javascript: javascript,
dart: dart,
ts: typescript,
typescript: typescript,
Expand Down Expand Up @@ -56,7 +57,8 @@ const languages = {
py: python,
rb: ruby,
cs: csharp,
css: css
css: css,
svelte: xml
} as const satisfies Record<string, LanguageFn>;

const platformAliases: Record<string, keyof typeof languages> = {
Expand Down
10 changes: 10 additions & 0 deletions src/routes/docs/tutorials/sveltekit-auth/+layout.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script lang="ts">
import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte';

export let data;
const tutorials = globToTutorial(data);
setContext('tutorials', tutorials);
</script>

<slot />
11 changes: 11 additions & 0 deletions src/routes/docs/tutorials/sveltekit-auth/+layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { LayoutLoad } from './$types';

export const load: LayoutLoad = ({ url }) => {
const tutorials = import.meta.glob('./**/*.markdoc', {
eager: true
});
return {
tutorials,
pathname: url.pathname
};
};
6 changes: 6 additions & 0 deletions src/routes/docs/tutorials/sveltekit-auth/+page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';

export const load: PageLoad = async () => {
throw redirect(303, '/docs/tutorials/sveltekit-auth/step-1');
};
18 changes: 18 additions & 0 deletions src/routes/docs/tutorials/sveltekit-auth/step-1/+page.markdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
layout: tutorial
title: Authentication with SvelteKit
description: Add Authentication to a SvelteKit project using Appwrite.
step: 1
difficulty: beginner
readtime: 20
---

Appwrite takes away your stress of building and maintaining a backend. Appwrite helps you implement authentication, databases, file storage, and respond to real-time events with **secure** APIs out of the box.
If you're a Svelte developer, examples in this guide shows you how Appwrite can help you add authentication to Svelte apps faster.

## Before you start

Even if you've never tried Appwrite, you will get an idea of what it'll feel like to build with Svelte and Appwrite.

If you're inspired and wish to follow along, make sure you've followed [Start with Svelte](https://appwrite.io/docs/quick-starts/sveltekit) first.
You can also choose to clone our [Getting started](https://github.com/appwrite/getting-started-projects/tree/main/svelte) examples and follow along by looking at our example code.
52 changes: 52 additions & 0 deletions src/routes/docs/tutorials/sveltekit-auth/step-2/+page.markdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
layout: tutorial
title: Create project
description: Add Authentication to a SvelteKit project using Appwrite.
step: 2
---


You can create a Svelte project using [SvelteKit](https://kit.svelte.dev/docs/creating-a-project).

```sh
npm create svelte@latest
```

The command will give you a prompt with several project types. We'll be starting with a skeleton project.

The prompt will be something similar to this.

```sh
create-svelte version 3.2.0

┌ Welcome to SvelteKit!
◇ Where should we create your project?
│ my-svelte-project
◇ Which Svelte app template?
│ Skeleton project
◇ Add type checking with TypeScript?
│ Yes, using JavaScript with JSDoc comments
◇ Select additional options (use arrow keys/space bar)
│ Add ESLint for code linting
└ Your project is ready!
```

After the prompt is finished, you can head over to the newly create project.

```sh
cd my-svelte-project
npm install
```

## Adding Appwrite to Your Svelte App

Appwrite provides a Web SDK that can be used in your Svelte apps. You can use Appwrite by installing the Web SDK as an NPM package.

```sh
npm install appwrite
```
46 changes: 46 additions & 0 deletions src/routes/docs/tutorials/sveltekit-auth/step-3/+page.markdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
layout: tutorial
title: Initialize SDK
description: Add Authentication to a SvelteKit project using Appwrite.
step: 3
---
Before you can use Appwrite, you need to instanciate the Appwrite `Client` class with the project ID and endpoint.
This tells the SDK where your Appwrite project is hosted and which one to connect to.

The client is then used to initialize services like `Databases` and `Account`, so they all point to the same Appwrite project.

You can do this by instantiating the services you need in a file like `src/lib/appwrite.js` and **exporting the instances**.

```js
// src/lib/appwrite.js
import {
PUBLIC_APPWRITE_ENDPOINT,
PUBLIC_APPWRITE_PROJECT,
} from "$env/static/public";
import { Databases, Account, Client } from "appwrite";

const client = new Client();
client
.setEndpoint(PUBLIC_APPWRITE_ENDPOINT)
.setProject(PUBLIC_APPWRITE_PROJECT);

const account = new Account(client);
const databases = new Databases(client);

export const appwrite = {
client,
account,
databases,
};
```

`PUBLIC_APPWRITE_ENDPOINT` and `PUBLIC_APPWRITE_PROJECT` are environment variables that are exported in your project's [.env file](https://kit.svelte.dev/docs/modules#$env-dynamic-public).

For example, your `.env` might look something similar to this.

```text
PUBLIC_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
PUBLIC_APPWRITE_PROJECT=642sdddf85b440dc7e5bf
```

You can get the values for these variables from the Appwrite console's **Settings** page.
64 changes: 64 additions & 0 deletions src/routes/docs/tutorials/sveltekit-auth/step-4/+page.markdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
layout: tutorial
title: Check if logged in
description: Add Authentication to a SvelteKit project using Appwrite.
step: 4
---

Before taking a user to the login screen, we should check if they're already logged in.
With SvelteKit, you can use the `load` function to check if you're logged in before your app renders.

```js
// src/routes/+layout.js
import { appwrite } from "$lib/appwrite";

// Turn off SSR globally, turning the project into a static site
export const ssr = false;

export const load = async () => {
try {
return {
account: await appwrite.account.get(),
};
} catch {
return {
account: null,
};
}
};
```

By returning the account data in the root layout, it is globally available to all pages, **before any page is displayed** to the user.
If we find the user is not logged in, we can redirect them to log in first.

We can get the returned data in the root page by using the `data` prop:

```svelte
<!-- src/routes/+page.svelte -->
<script>
import { invalidateAll } from '$app/navigation';
import { appwrite } from '$lib/appwrite.js';

export let data;

$: loggedIn = !!data.account;

async function logout() {
await appwrite.account.deleteSession('current');
// invalidateAll will execute all `load` functions again.
// In our case, this means we'll fetch the account data again.
await invalidateAll();
}
</script>

{#if loggedIn}
<p>Hello {data.account?.name}!</p>
<button on:click={logout}>Logout</button>
{:else}
<a href="/login">Login</a>
<a href="/signup">Signup</a>
{/if}
```

Normally, you'll want to use the `invalidate` method instead of `invalidateAll`, to avoid fetching data that you don't need to.
You can refer to the [SvelteKit docs](https://kit.svelte.dev/docs/modules#$app-navigation-invalidate) for more information on invalidation.
83 changes: 83 additions & 0 deletions src/routes/docs/tutorials/sveltekit-auth/step-5/+page.markdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
layout: tutorial
title: Create login page
description: Add Authentication to a SvelteKit project using Appwrite.
step: 5
---

We can now implement our login page. Create a `+page.js` file in the `src/routes/login` directory:

```js
// src/routes/login/+page.js
import { redirect } from "@sveltejs/kit";

export const load = async ({ parent }) => {
// Gets the data returned from the root layout
const { account } = await parent();
if (account) {
throw redirect(303, "/");
}
};
```

You can see that we added a redirect in the login page to check if the user's already logged in, in which case we redirect them to the homepage.

Now we just need to create a form to let the user input sign in data.

```svelte
<!-- src/routes/login/+page.svelte -->
<script>
import { invalidateAll } from '$app/navigation';
import { appwrite } from '$lib/appwrite';

/** @type {string|null} */
let formError = null;

/**
* @param {Event} event
*/
async function handleSubmit(event) {
event.preventDefault();
formError = null;

const form = /** @type {HTMLFormElement} */ (event.target);
const formData = /** @type Record<string, string | undefined> */ (
Object.fromEntries(new FormData(form).entries())
);

const { email, password } = formData;
if (!email || !password) {
formError = 'Please fill out all fields';
return;
}

try {
await appwrite.account.createEmailSession(email, password);
await invalidateAll();
} catch (e) {
formError = /** @type {import('appwrite').AppwriteException} */ (e).message;
}
}
</script>

<form on:submit={handleSubmit}>
<label for="email">Email</label>
<input type="email" id="email" name="email" required />
<label for="password">Password</label>
<input
type="password"
id="password"
name="password"
placeholder="SuperSecretPassword"
required
minlength="8"
/>
<button type="submit">Login</button>
{#if formError}
<p>{formError}</p>
{/if}
</form>
```

And that's it. When the user successfully logs in, we use `invalidateAll` to re-run the relevant `load` functions. In this case, the functions inside `src/routes/login/+page.js` and `src/routes/+layout.js`.
This means that the `account` data will be updated, no longer being `null`, and so the login `load` function will redirect the user to the home page.
69 changes: 69 additions & 0 deletions src/routes/docs/tutorials/sveltekit-auth/step-6/+page.markdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
layout: tutorial
title: Create signup page
description: Add Authentication to a SvelteKit project using Appwrite.
step: 6
---

For signup, you can copy the login `+page.js` and `+page.svelte` files into `src/routes/signup`, with some small changes to the `+page.svelte` file:

```svelte
<!-- src/routes/signup/+page.svelte -->
<script>
import { invalidateAll } from '$app/navigation';
import { appwrite } from '$lib/appwrite';
import { ID } from "appwrite";

/** @type {string|null} */
let formError = null;

/**
* @param {Event} event
*/
async function handleSubmit(event) {
event.preventDefault();
formError = null;

const form = /** @type {HTMLFormElement} */ (event.target);
const formData = /** @type Record<string, string | undefined> */ (
Object.fromEntries(new FormData(form).entries())
);

const { name, email, password } = formData;
if (!name || !email || !password) {
formError = 'Please fill out all fields';
return;
}

try {
await appwrite.account.create(ID.unique(), email, password, name);
await appwrite.account.createEmailSession(email, password);
await invalidateAll();
} catch (e) {
formError = /** @type {import('appwrite').AppwriteException} */ (e).message;
}
}
</script>

<form on:submit={handleSubmit}>
<label for="name">Name</label>
<input type="text" id="name" name="name" required />
<label for="email">Email</label>
<input type="email" id="email" name="email" required />
<label for="password">Password</label>
<input
type="password"
id="password"
name="password"
placeholder="SuperSecretPassword"
required
minlength="8"
/>
<button type="submit">Signup</button>
{#if formError}
<p>{formError}</p>
{/if}
</form>
```
With this, you have a simple authentication system.

Loading