Full-Stack Development with
Svelte and SvelteKit
The Trainer
- Nils Röhrig, 37
- Software Engineer @ Loql
- Frontend focus
- Svelte user since 2019
- Speaker at conferences and meetups
Agenda
Part I
- Svelte Fundamentals
- Svelte Project Live Coding
- Hands-On Project Work
- Demonstration of Project Solution
Part II
- SvelteKit & Libs Fundamentals
- SvelteKit Project Live Coding
- Hands-On Project Work
- Demonstration of Project Solution
Coffee Break
10:00 - 10:30
Lunch Break
12:00 - 13:00
Coffee Break
14:30 - 15:00
Begin
09:00
End
17:00
What is Svelte?
Svelte is a tool for building web applications. Like other user interface frameworks, it allows you to build your app declaratively out of components that combine markup, styles and behaviours.
Svelte is a component framework
Svelte is a component framework
Logo
Register
Home
E-Mail
Submit
etc. pp...
Component
Component
Component
These components are compiled into small, efficient JavaScript modules that eliminate overhead traditionally associated with UI frameworks.
Svelte is a compiler
Svelte is a compiler
<h1>Simple Component</h1>
Component.svelte
Svelte is a compiler
<h1>Simple Component</h1>
Svelte is a compiler
<h1>Simple Component</h1>
Svelte is a compiler
<h1>Simple Component</h1>
import { SvelteComponent, detach, element, init,
insert, noop, safe_not_equal } from "svelte/internal";
function create_fragment(ctx) {
let h1;
return {
c() {
h1 = element("h1");
h1.textContent = "Simple Component";
},
m(target, anchor) {
insert(target, h1, anchor);
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(h1);
},
};
}
class SimpleComponent extends SvelteComponent {
constructor(options) {
super();
init(this, options, null, create_fragment, safe_not_equal, {});
}
}
export default SimpleComponent;
Svelte is a compiler
<h1>Simple Component</h1>
import { [...] } from "svelte/internal";
function create_fragment(ctx) {
let h1;
return {
c() {
h1 = element("h1");
h1.textContent = "Simple Component";
},
m(target, anchor) {
insert(target, h1, anchor);
},
p: noop, i: noop, o: noop,
d(detaching) { if (detaching) detach(h1); },
};
}
class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, null, create_fragment,
safe_not_equal, {});
}
}
export default Component;
Component.js
Svelte is a compiler
<h1>Simple Component</h1>
import Component from "./Component.js";
const app = new Component({
target: document.body,
});
export default app;
main.js
Anatomy of a component
Anatomy of a component
<script>
// STATE & BEHAVIOR
</script>
<!-- DECLARATIVE MARKUP -->
<h1>Hello enterJS!</h1>
<p>{"Put your JS expressions in curly braces."}</p>
<style>
/* PRESENTATION */
</style>
Component.svelte
Local State
Local State
- The local component state in Svelte consists of local variables.
- It describes the state of the component and it is not shared with other components.
- The local state is reactive in the Svelte sense, meaning that only the affected parts of the component are updated when changes occur.
Local State
<script>
let count = 1
</script>
<output>
{count} + {count} = {count + count}
</output>
LocalState.svelte
Directives
Directives
- Svelte directives can be used as attributes in Svelte elements.
- They can control the behavior of elements, such as binding data or handling events.
- Svelte provides various directives that can control the behavior of elements, such as
on:
andbind:
.
Directives
<script>
let count = 1
</script>
<input bind:value={count} />
<button on:click={() => count-- }>Decrease</button>
<button on:click={() => count-- }>Increase</button>
<output> Count: {count} </output>
Counter.svelte
Logic Blocks
Logic Blocks
- Logic blocks are an integral part of Svelte markup.
- They control the program flow within Svelte markup and are known from other programming languages.
- Svelte provides various control structures in form of logic blocks, like
{#if}
&{:else}
for conditionals and{#each}
for loops.
- There is also a logic block to declaratively work with promises, which is aptly named
{#await}
.
<script>
let condition = false
</script>
<p> <input type="checkbox" bind:checked={condition} /> </p>
{#if condition}
<p>The condition is true!</p>
{:else}
<p>The condition is false!</p>
{/if}
IfElse.svelte
Logic Blocks
<script>
let promise = fetchSomething()
</script>
{#await promise}
<p>Loading...</p>
{:then value}
<p>The promise resolved with value '{value}'</p>
{:catch error}
<p>The promise rejected with value '{error}'</p>
{/await}
Await.svelte
Logic Blocks
Logic Blocks
<script>
let someIterable = [1, 2, 3, 4, 5]
</script>
<ul>
{#each someIterable as item}
<li>{item}</li>
{:else value}
<li>No items found</li>
{/each}
</ul>
Each.svelte
Logic Blocks
- If the value of an
{#each}
block in Svelte is changed, elements are removed or added only at the end of the block.
- Changed elements are updated at the same position in the DOM.
- Using unique IDs can avoid unwanted effects if rendered elements are not directly or reactively dependent on the change of the value.
Local Constants
Local Constants
- Some calculations have to happen in a component occasionally.
- If the calculation depends on a value within a logic block, this can get tricky or cumbersome if the result is needed multiple times.
- Local constants defined with the
{@const}
tag can help with that.
Local Constants
<script>
let edges = [2, 3, 4];
</script>
<h1>Cube calculations</h1>
{#each edges as edge}
{@const square = edge * edge}
{@const surface = 6 * square}
{@const volume = square * edge}
<output>
Edge length: {edge}m,
Total Surface: {surface}m²,
Volume: {volume}m³
</output>
{/each}
Cubes.svelte
Reactivity
Reactivity
- Svelte integrates reactivity as a core element.
- The
=
operator and its variants are used in Svelte to establish data binding between variables and UI elements.
- Svelte tracks dependencies between variables and only updates the parts of the DOM that are affected by them.
- By tracking assignments at build time, the code is optimized.
- Svelte provides language support such as Reactive Statements.
Reactivity
<script>
let counter = 0;
const inc = () => counter++;
const dec = () => counter--;
</script>
<output>{counter}</output>
<button on:click={dec}>Decrease</button>
<button on:click={inc}>Increase</button>
Counter.svelte
Reactivity
<script>
let counter = 0;
$: doubled = counter * 2;
$: quadrupled = doubled * 2;
const inc = () => counter++;
const dec = () => counter--;
</script>
<b>x1</b>: {counter}<br>
<b>x2</b>: <output>{counter} * 2 = {doubled}</output><br>
<b>x4</b>: <output>{doubled} * 2 = {quadrupled}</output><br>
<button on:click={dec}>Decrease</button>
<button on:click={inc}>Increase</button>
Counter.svelte
CSS Support
CSS Support
- CSS is part of the Single-File-Component approach in Svelte.
- Styles are automatically scoped to avoid conflicts.
- The
class:
directive allows binding classes based on expressions.
- The
style:
directive allows setting individual style properties to expressed values.
<script>
import P from './P.svelte'
let isBlue = false
</script>
<input type="checkbox" bind:checked={isBlue} />
<h2 style:color={isBlue ? "blue" : ""}>
This is blue, when isBlue is true.</h2>
<h2 class:isBlue>
This is blue, when isBlue is true.</h2>
<p> This is always red. </p>
<P> This is never red. </P>
<style>
.isBlue { color: blue; }
p { color: red; }
</style>
CSS.svelte
<p><slot /></p>
P.svelte
CSS Support
Example in REPL
Nesting of components
Nesting of components
Logo
Register
Home
E-Mail
Submit
Nesting of components
E-Mail
Submit
Register
Home
Logo
Logo
Register
Home
E-Mail
Submit
Logo
Register
Home
Submit
E-Mail
Submit
Register
Home
Logo
Logo
Register
Home
E-Mail
Submit
E-Mail
<Logo>
<Header>
<App>
<NavItem>
<NavItem>
<NavBar>
<Label>
<Input>
<Form>
<Button>
Logo
Register
Home
E-Mail
Submit
E-Mail
Submit
Register
Home
Logo
Logo
Register
Home
E-Mail
Submit
<script>
import Header from './Header.svelte'
import NavBar from './NavBar.svelte'
import Form from './Form.svelte'
</script>
<div class="wrapper">
<Header />
<NavBar />
<Form />
</div>
App.svelte
Nesting of components
If I nest components within each other, how am I supposed to provide data from one to the other?
- Some person, somewhere
Component Props
Component Props
Register
Home
<NavItem>
<NavItem>
Register
Home
Component Props
<NavItem >
<NavItem >
label="Home"
label="Register"
Register
Home
Component Props
Props
<script>
const str = "a string";
const num = 12345;
const bool = true;
const obj = {key: "value"};
const arr = [1, 2, 3, 4, 5];
function callback() {
console.log("callback");
}
</script>
<AnyComponent
stringProp={str}
numberProp={num}
booleanProp={bool}
objectProp={obj}
arrayProp={arr}
{callback}
/>
Props
<script>
export let stringProp = "";
export let numberProp = 0;
export let booleanProp = true;
export let objectProp = {key: "value"};
export let arrayProp = [1, 2, 3];
export let callback = () => undefined;
</script>
<p>{stringProp}</p>
<p>{numberProp}</p>
<p>{booleanProp}</p>
{#each Object.entries(objectProp) as [key, value]}
<p>{key}: {value}</p>
{/each}
{#each arrayProp as value}
<p>{value}</p>
{/each}
<button on:click={callback}>Call back</button>
Event Dispatching
Event Dispatching
E-Mail
Submit
Register
Home
Logo
Event Dispatching
E-Mail
Submit
Logo
Register
Home
E-Mail
Submit
<App>
<Form>
Event Dispatching
E-Mail
Submit
Logo
Register
Home
E-Mail
Submit
<App>
<Form>
How?
Event Dispatching
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
async function submitForm() {
await postForm();
dispatch("form:posted", { /* any information to share */ });
}
</script>
<form on:submit={submitForm}>
<!-- form content -->
</form>
Form.svelte
Event Dispatching
<script>
import Form from './Form.svelte';
function handleSubmit(event) {
/* here the shared information can be found,
* as Svelte events are just instances of
* CustomEvent */
const { detail } = event;
console.log({detail});
}
</script>
<Form on:form:posted={handleSubmit} />
App.svelte
Component Slots
Component Slots
- Slots in Svelte allow for the dynamic injection of content into components.
- They are defined in components using the
<slot>
element.
- Slot contents are provided by parent components.
- Slot contents can contain any valid Svelte markup.
Component Slots
Dialog
Text that is shown in the dialog. Potentially expanded by further content.
Okay
Component Slots
<strong>Dialog</strong>
<p>Text that is shown in the dialog. Potentially expanded by further content.</p>
<button>Okay</button>
Component Slots
<dialog>
<header><slot name="header" /></header>
<slot />
<footer><slot name="footer" /></footer>
</dialog>
Dialog.svelte
<script>
import Dialog from './Dialog.svelte'
</script>
<Dialog>
<h1 slot="header">Dialog Header (header slot)</h1>
<p>Dialog content (default slot)</p>
<svelte:fragment slot="footer">
<button>Cancel</button>
<button>Okay</button>
</svelte:fragment>
</Dialog>
App.svelte
Component Slots
<dialog>
<header><slot name="header" /></header>
<slot />
<footer><slot name="footer" /></footer>
</dialog>
Dialog.svelte
<script>
import Dialog from './Dialog.svelte'
</script>
<Dialog>
<h1 slot="header">Dialog Header (header slot)</h1>
<p>Dialog content (default slot)</p>
<svelte:fragment slot="footer">
<button>Cancel</button>
<button>Okay</button>
</svelte:fragment>
</Dialog>
App.svelte
Component Slots
<dialog>
<header><slot name="header" /></header>
<slot />
<footer><slot name="footer" /></footer>
</dialog>
Dialog.svelte
<script>
import Dialog from './Dialog.svelte'
</script>
<Dialog>
<h1 slot="header">Dialog Header (header slot)</h1>
<p>Dialog content (default slot)</p>
<svelte:fragment slot="footer">
<button>Cancel</button>
<button>Okay</button>
</svelte:fragment>
</Dialog>
App.svelte
Component Slots
<dialog>
<header><slot name="header" /></header>
<slot />
<footer><slot name="footer" /></footer>
</dialog>
Dialog.svelte
<script>
import Dialog from './Dialog.svelte'
</script>
<Dialog>
<h1 slot="header">Dialog Header (header slot)</h1>
<p>Dialog content (default slot)</p>
<svelte:fragment slot="footer">
<button>Cancel</button>
<button>Okay</button>
</svelte:fragment>
</Dialog>
App.svelte
Let's get started with the Svelte project!
Hands-On Project Work (Part I)
Project Solution (Part I)
Part II
What is SvelteKit?
SvelteKit is a framework for rapidly developing robust, performant web applications using Svelte. If you're coming from React, SvelteKit is similar to Next. If you're coming from Vue, SvelteKit is similar to Nuxt.
- Components
- Rendering
- Interactivity
- ‘Browser Stuff‘
- Routing
- Data Loading
- Form Processing
- ‘Server Stuff‘
Routing
Routing
- Routing in SvelteKit is file-based.
- The path of a route is defined by it's respective location in the file system.
-
src/routes/
points to the root route.
-
src/routes/login
points to a /login route.
-
src/routes/posts/[id]
points to a post with the id passed in place of[id]
.
Routing
+page.svelte
The pages markup, styles and behavior. It may receive a data
or a form
prop.
Route/[param]
The actual path to a specific route, rooted at the routes
folder in a SvelteKit code base.
Loads data on the client and on the server. Returns data available within the data
prop.
+page.js
Loads data and processes forms on the server. Returns data available within data
and form
.
+page.server.js
Routing
<script>
// data loaded in
// load functions
export let data
// data returned from
// form actions
export let form
</script>
<h1>Route Headline</h1>
<!-- any additional markup -->
+page.svelte
export function load () {
// any data loading logic
// that can run on client
// and server
return {
// any loaded data
}
}
+page.js
export function load () {
// any data loading logic
// that is server-only
return {
// any loaded data
}
}
export const actions = {
default() {
// default form action
},
namedAction() {
// named form action
}
}
+page.server.js
Routing
<script>
// data loaded in
// load functions
export let data
// data returned from
// form actions
export let form
</script>
<h1>Route Headline</h1>
<!-- any additional markup -->
+page.svelte
export function load () {
// any data loading logic
// that can run on client
// and server
return {
// any loaded data
}
}
+page.js
export function load () {
// any data loading logic
// that is server-only
return {
// any loaded data
}
}
export const actions = {
default() {
// default form action
},
namedAction() {
// named form action
}
}
+page.server.js
Routing
<script>
// data loaded in
// load functions
export let data
// data returned from
// form actions
export let form
</script>
<h1>Route Headline</h1>
<!-- any additional markup -->
+page.svelte
export function load () {
// any data loading logic
// that can run on client
// and server
return {
// any loaded data
}
}
+page.js
export function load () {
// any data loading logic
// that is server-only
return {
// any loaded data
}
}
export const actions = {
default() {
// default form action
},
namedAction() {
// named form action
}
}
+page.server.js
Routing
<script>
// data loaded in
// load functions
export let data
// data returned from
// form actions
export let form
</script>
<h1>Route Headline</h1>
<!-- any additional markup -->
+page.svelte
export function load () {
// any data loading logic
// that can run on client
// and server
return {
// any loaded data
}
}
+page.js
export function load () {
// any data loading logic
// that is server-only
return {
// any loaded data
}
}
export const actions = {
default() {
// default form action
},
namedAction() {
// named form action
}
}
+page.server.js
Routing
<script>
// data loaded in
// load functions
export let data
// data returned from
// form actions
export let form
</script>
<h1>Route Headline</h1>
<!-- any additional markup -->
+page.svelte
export function load () {
// any data loading logic
// that can run on client
// and server
return {
// any loaded data
}
}
+page.js
export function load () {
// any data loading logic
// that is server-only
return {
// any loaded data
}
}
export const actions = {
default() {
// default form action
},
namedAction() {
// named form action
}
}
+page.server.js
Layouts
Layouts
- Like routes, layouts in SvelteKit are file-based.
- By default, layouts apply to the route that is specified in the same folder and all of it's decencdants.
- For example,
src/routes/+layout.svelte
applies to-
src/routes/+page.svelte
, as well as -
src/routes/login/+page.svelte
, etc.
-
- Layouts are also inherit from other layouts specified higher in the file-system tree
- For example,
src/routes/+layout.svelte
wrapssrc/routes/login/+layout.svelte
Your+layout.svelte
files can also load data, via+layout.js
or+layout.server.js
.
Layouts
<script>
export let data
</script>
<div class="wrapper">
<header>Headline</header>
<main>
<slot />
</main>
<footer>Footer</footer>
</div>
+layout.svelte
Data Loading
Data Loading
- SvelteKit differentiates between universal load functions (located in
+page.js
) and server load functions (located in+page.server.js
).
- When requesting a page for the first time, both functions run on the server, with the server load function running first.
- After that, universal load functions will only be invoked in the browser.
- The same rules apply to load function associated with layouts (
+layout.js
and+layout.server.js
, respectively).
- Layout and page load functions run in parallel. If that is not desired, the data of the parent load function can be awaited, in which case the affected load functions run sequentially.
export function load () {
return {
someFlag: true
}
}
routes/+layout.server.js
export function load (event) {
const { parent } = event
const data = await parent();
return {
anotherFlag: !data.someFlag
}
}
routes/posts/+layout.server.js
export function load (event) {
const { parent } = event
const data = await parent();
const {
someFlag,
anotherFlag
} = data;
return {
// data based on someFlag
// and anotherFlag
}
}
routes/posts/+page.server.js
Data Loading
export function load () {
return {
someFlag: true
}
}
routes/+layout.server.js
export function load (event) {
const { parent } = event
const data = await parent();
return {
anotherFlag: !data.someFlag
}
}
routes/posts/+layout.server.js
export function load (event) {
const { parent } = event
const data = await parent();
const {
someFlag,
anotherFlag
} = data;
return {
// data based on someFlag
// and anotherFlag
}
}
routes/posts/+page.server.js
Data Loading
export function load () {
return {
someFlag: true
}
}
routes/+layout.server.js
export function load (event) {
const { parent } = event
const data = await parent();
return {
anotherFlag: !data.someFlag
}
}
routes/posts/+layout.server.js
export function load (event) {
const { parent } = event
const data = await parent();
const {
someFlag,
anotherFlag
} = data;
return {
// data based on someFlag
// and anotherFlag
}
}
routes/posts/+page.server.js
Data Loading
export function load () {
return {
someFlag: true
}
}
routes/+layout.server.js
export function load (event) {
const { parent } = event
const data = await parent();
return {
anotherFlag: !data.someFlag
}
}
routes/posts/+layout.server.js
export function load (event) {
const { parent } = event
const data = await parent();
const {
someFlag,
anotherFlag
} = data;
return {
// data based on someFlag
// and anotherFlag
}
}
routes/posts/+page.server.js
Data Loading
export function load () {
return {
someFlag: true
}
}
routes/+layout.server.js
export function load (event) {
const { parent } = event
const data = await parent();
return {
anotherFlag: !data.someFlag
}
}
routes/posts/+layout.server.js
export function load (event) {
const { parent } = event
const data = await parent();
const {
someFlag,
anotherFlag
} = data;
return {
// data based on someFlag
// and anotherFlag
}
}
routes/posts/+page.server.js
Data Loading
export function load ({ params }) {
const post = await getPost(params.id)
return {
post
}
}
routes/posts/[id]/+page.server.js
Data Loading
Form Actions
Form Actions
- Form actions are a way to process native HTML forms in SvelteKit.
- They are defined in a
+page.server.ts
file alongside the load function.
- It's possible to either define one default action or an arbitrary amount of named actions.
- Form actions accept requests that are executed via a
<form>
element with thePOST
method.
- Since they are only processed on the server, there is no need for client-side JavaScript for basic forms.
- To improve the user experience, SvelteKit provides an easy way to progressively enhance forms via the
use:enhance
Svelte action.
- Data returned by form actions is accessible via the pages
form
prop.
Form Actions
<script>
import { enhance } from '$app/forms';
export let form;
</script>
<form method="POST" use:enhance>
{#if !form.success}
An error occurred!
{/if}
<input type="email" name="email">
<input type="password" name="password">
<button>Submit</button>
</form>
+page.svelte
import { fail } from '@sveltejs/kit';
export const actions = {
default({ request }) {
const formData = await request.formData();
const success = validateCredentials(
formData.get("email"),
formData.get("password")
)
if(!success) {
return fail(400, { success: false });
}
return { success: true };
}
}
+page.server.js
Form Actions
<script>
import { enhance } from '$app/forms';
export let form;
</script>
<form method="POST" use:enhance>
{#if !form.success}
An error occurred!
{/if}
<input type="email" name="email">
<input type="password" name="password">
<button>Submit</button>
</form>
+page.svelte
import { fail } from '@sveltejs/kit';
export const actions = {
default({ request }) {
const formData = await request.formData();
const success = validateCredentials(
formData.get("email"),
formData.get("password")
)
if(!success) {
return fail(400, { success: false });
}
return { success: true };
}
}
+page.server.js
Form Actions
<script>
import { enhance } from '$app/forms';
export let form;
</script>
<form method="POST" use:enhance>
{#if !form.success}
An error occurred!
{/if}
<input type="email" name="email">
<input type="password" name="password">
<button>Submit</button>
</form>
+page.svelte
import { fail } from '@sveltejs/kit';
export const actions = {
default({ request }) {
const formData = await request.formData();
const success = validateCredentials(
formData.get("email"),
formData.get("password")
)
if(!success) {
return fail(400, { success: false });
}
return { success: true };
}
}
+page.server.js
Form Actions
<script>
import { enhance } from '$app/forms';
export let form;
</script>
<form method="POST" use:enhance>
{#if !form.success}
An error occurred!
{/if}
<input type="email" name="email">
<input type="password" name="password">
<button>Submit</button>
</form>
+page.svelte
import { fail } from '@sveltejs/kit';
export const actions = {
default({ request }) {
const formData = await request.formData();
const success = validateCredentials(
formData.get("email"),
formData.get("password")
)
if(!success) {
return fail(400, { success: false });
}
return { success: true };
}
}
+page.server.js
Error Handling
Error Handling
- In SvelteKit, errors are distinguished into expected errors and unexpected errors.
- Expected errors are purposefully created by the developer. An example would be a 401 error when a request is unauthorized. They are created by using the
error()
helper provided by SvelteKit.
- As the name says, unexpected errors are errors that occur unexpectedly.
- Information and status code associated with an expected error are defined by the developer.
- Unexpected errors always have the status code 500 and only send a generic message to the client, to avoid leaking secrets.
- In both cases, an error page is displayed, which can be customized by creating a
+error.svelte
file for the affected route hierarchy.
- Unexpected errors can also be handled within the
handleError
hook.
export function throwError() {
throw new Error();
}
some-module.js
import { error } from '@sveltejs/kit';
import { throwError } from './someModule.js'
export function load() {
if(!userIsAuthorized()) {
// expected error
error(401, {
message: "The user is not authorized."
});
}
// unexpected error
throwError()
return {
// ...data
}
}
+page.server.js
Error Handling
Hooks
Hooks
- Hooks are functions with an app-wide effect, which can be used to control certain aspects of the frameworks behavior.
- SvelteKit differentiates between server hooks, shared hooks and universal hooks.
- As suggested by their respective names, server hooks can only run on the server, shared hooks run on either the client or the server and universal hooks run in both places.
- The handle() hook, for example, is a server only hook which runs every time, the server receives a request, making it useful to handle authentication or using middlewares.
- The handleError() hook on the other hand, is a shared hook and can run on either the client or the server. It allows the handling of unexpected errors in a centralized place.
export function handleError({error}) {
// do error handling stuff
return {
message: "Error message",
//... other data
}
}
hooks.client.js
import { redirect } from '@sveltejs/kit';
export function handle({event, resolve}) {
if(event.url.pathname.startsWith("/api")
&& !userIsAuthorized()) {
redirect(302, "/login")
}
return resolve(event)
}
hooks.server.js
Hooks
export function handleError({error}) {
// do error handling stuff
return {
message: "Error message",
//... other data
}
}
hooks.client.js
import { redirect } from '@sveltejs/kit';
export function handle({event, resolve}) {
if(event.url.pathname.startsWith("/api")
&& !userIsAuthorized()) {
redirect(302, "/login")
}
return resolve(event)
}
hooks.server.js
Hooks
export function handleError({error}) {
// do error handling stuff
return {
message: "Error message",
//... other data
}
}
hooks.client.js
import { redirect } from '@sveltejs/kit';
export function handle({event, resolve}) {
if(event.url.pathname.startsWith("/api")
&& !userIsAuthorized()) {
redirect(302, "/login")
}
return resolve(event)
}
hooks.server.js
Hooks
export function handleError({error}) {
// do error handling stuff
return {
message: "Error message",
//... other data
}
}
hooks.client.js
import { redirect } from '@sveltejs/kit';
export function handle({event, resolve}) {
if(event.url.pathname.startsWith("/api")
&& !userIsAuthorized()) {
redirect(302, "/login")
}
return resolve(event)
}
hooks.server.js
Hooks
Zod
Zod
- Zod is a library for runtime type validation. It is not part of SvelteKit.
- At the heart of Zod is the definition of schemas, which are then used to parse JavaScript objects to match said schemas.
- When using the
Schema.parse()
method, the result is either an object matching the schema or an error is thrown.
- When using the
Schema.safeParse()
method, the result is always an object with a success property and the parsed data or the error.
- When TypeScript is used, Zod parsing creates type safety for the compiler as well.
import { z } from 'zod';
export const User = z.object({
name: z.string(),
age: z.number().optional(),
dateOfBirth: z.coerce.date()
})
// passes
let user = User.parse({name: "Karl", dateOfBirth: new Date()})
// throws
let user = User.parse({name: 3537, dateOfBirth: new Date() })
// throws
let user = User.parse({name: "Marlene", dateOfBirth: "not-a-date" })
User.js
Zod
zod-form-data
zod-form-data
- zod-form-data is a small library leveraging Zod to validate FormData objects. It's also not part of SvelteKit but pairs very well with form actions.
- Like Zod, zod-form-data allows the definition of schemas, but specifically for FormData objects instead of JS objects.
- It's possible to either use the validators, zod-form-data provides, or providing a Zod schema, which zod-form-data uses to validate a given .
- Parsing works the same as in Zod.
import { zfd } from 'zod-form-data';
const Credentials = zfd.formData({
email: zfd.text(),
password: zfd.text()
});
export const actions = {
async login({request}) {
const credentials = Credentials.parse(
await request.formData()
);
const user = await loginWithCredentials(credentials);
return {
user
}
}
}
+page.server.js
zod-form-data
TypeORM
TypeORM
- TypeORM is a library for object relational mapping. It offers an object-oriented way to use data from relational databases. It is not part of SvelteKit.
- At the core are objects called entities, which are mapped to rows of tables in a database.
- Most of the database logic is handled by the library, developers need only to work with the entities most of the time.
- It may be called TypeORM and works best with TypeScript, but it also supports plain JavaScript if desired.
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({
type: 'varchar',
length: 254
})
email: string;
@Column({
type: 'varchar',
length: 100,
})
password: string;
}
User.ts
zod-form-data
const userRepository = datasource.getRepository(User);
function async addUser(email: string, password: string) {
const user = new User();
user.email = email;
user.password = encrypt(password);
await userRepository.save(user);
}
function async getUser(id: number) {
const user = userRepository.findOne({
where: {
id: id
}
})
}
function async deleteUser(id: number) {
await userRepository.delete(id);
}
userManagement.ts
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({
type: 'varchar',
length: 254
})
email: string;
@Column({
type: 'varchar',
length: 100,
})
password: string;
}
User.ts
zod-form-data
const userRepository = datasource.getRepository(User);
function async addUser(email: string, password: string) {
const user = new User();
user.email = email;
user.password = encrypt(password);
await userRepository.save(user);
}
function async getUser(id: number) {
const user = userRepository.findOne({
where: {
id: id
}
})
}
function async deleteUser(id: number) {
await userRepository.delete(id);
}
userManagement.ts
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({
type: 'varchar',
length: 254
})
email: string;
@Column({
type: 'varchar',
length: 100,
})
password: string;
}
User.ts
zod-form-data
const userRepository = datasource.getRepository(User);
function async addUser(email: string, password: string) {
const user = new User();
user.email = email;
user.password = encrypt(password);
await userRepository.save(user);
}
function async getUser(id: number) {
const user = userRepository.findOne({
where: {
id: id
}
})
}
function async deleteUser(id: number) {
await userRepository.delete(id);
}
userManagement.ts
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({
type: 'varchar',
length: 254
})
email: string;
@Column({
type: 'varchar',
length: 100,
})
password: string;
}
User.ts
zod-form-data
const userRepository = datasource.getRepository(User);
function async addUser(email: string, password: string) {
const user = new User();
user.email = email;
user.password = encrypt(password);
await userRepository.save(user);
}
function async getUser(id: number) {
const user = userRepository.findOne({
where: {
id: id
}
})
}
function async deleteUser(id: number) {
await userRepository.delete(id);
}
userManagement.ts
Let's get started with the SvelteKit project!
Hands-On Project Work (Part II)
Project Solution (Part II)
LinkedIn:
Xing:
X:
Thank You!
Full-Stack Development with Svelte and SvelteKit
By Nils Röhrig
Full-Stack Development with Svelte and SvelteKit
- 767