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

  1. Svelte Fundamentals
  2. Svelte Project Live Coding
  3. Hands-On Project Work
  4. Demonstration of Project Solution

Part II

  1. SvelteKit & Libs Fundamentals
  2. SvelteKit Project Live Coding
  3. Hands-On Project Work
  4. 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: and bind:.

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 wraps src/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 the POST 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