|
| 1 | +--- |
| 2 | +title: "Build a SvelteKit App" |
| 3 | +description: "A tutorial on building SvelteKit applications with Deno. Learn how to set up a SvelteKit project, implement file-based routing, manage state with load functions, and create a full-stack TypeScript application." |
| 4 | +url: /examples/svelte_tutorial/ |
| 5 | +oldUrl: |
| 6 | + - /runtime/manual/examples/how_to_with_npm/svelte/ |
| 7 | + - /runtime/tutorials/how_to_with_npm/svelte/ |
| 8 | +--- |
| 9 | + |
| 10 | +[SvelteKit](https://kit.svelte.dev/) is a web framework built on top of |
| 11 | +[Svelte](https://svelte.dev/), a modern front-end compiler that builds highly |
| 12 | +optimized vanilla JavaScript. SvelteKit provides features like file-based |
| 13 | +routing, server-side rendering, and full-stack capabilities. |
| 14 | + |
| 15 | +In this tutorial we'll build a simple SvelteKit app with Deno. The app will |
| 16 | +display a list of dinosaurs. When you click on one, it'll take you to a dinosaur |
| 17 | +page with more details. You can see the |
| 18 | +[finished app on GitHub](https://github.com/denoland/tutorial-with-svelte). |
| 19 | + |
| 20 | +You can see a live version of the app on |
| 21 | +[Deno Deploy](https://tutorial-with-svelte.deno.deno.net/). |
| 22 | + |
| 23 | +:::info Deploy your own |
| 24 | + |
| 25 | +You can deploy your own version of this svelte app to Deno Deploy immediately. |
| 26 | +Just click the button! |
| 27 | + |
| 28 | +[](https://app.deno.com/new?clone=https://github.com/denoland/tutorial-with-svelte) |
| 29 | + |
| 30 | +::: |
| 31 | + |
| 32 | +## Create a SvelteKit app with Deno |
| 33 | + |
| 34 | +We'll use [SvelteKit](https://kit.svelte.dev/) to create a new SvelteKit app. In |
| 35 | +your terminal, run the following command to create a new SvelteKit app: |
| 36 | + |
| 37 | +```shell |
| 38 | +deno run -A npm:create-svelte |
| 39 | +``` |
| 40 | + |
| 41 | +When prompted, give your app a name and select the "Skeleton project" template. |
| 42 | +Choose "Yes, using TypeScript syntax" when asked about TypeScript. |
| 43 | + |
| 44 | +Once created, `cd` into your new project and run the following command to |
| 45 | +install dependencies: |
| 46 | + |
| 47 | +```shell |
| 48 | +deno install |
| 49 | +``` |
| 50 | + |
| 51 | +Then, run the following command to serve your new SvelteKit app: |
| 52 | + |
| 53 | +```shell |
| 54 | +deno task dev |
| 55 | +``` |
| 56 | + |
| 57 | +Deno will run the `dev` task from the `package.json` file which will start the |
| 58 | +Vite development server. Click the output link to localhost to see your app in |
| 59 | +the browser. |
| 60 | + |
| 61 | +## Configure the formatter |
| 62 | + |
| 63 | +`deno fmt` supports Svelte files with the |
| 64 | +[`--unstable-component`](https://docs.deno.com/runtime/reference/cli/fmt/#formatting-options-unstable-component) |
| 65 | +flag. To use it, run this command: |
| 66 | + |
| 67 | +```sh |
| 68 | +deno fmt --unstable-component |
| 69 | +``` |
| 70 | + |
| 71 | +To configure `deno fmt` to always format your Svelte files, add this at the top |
| 72 | +level of your `deno.json` file: |
| 73 | + |
| 74 | +```json |
| 75 | +"unstable": ["fmt-component"] |
| 76 | +``` |
| 77 | + |
| 78 | +## Add a backend API |
| 79 | + |
| 80 | +We'll build API routes using SvelteKit's built-in API capabilities. SvelteKit |
| 81 | +allows you to create API endpoints by creating `+server.js` or `+server.ts` |
| 82 | +files in your routes directory. |
| 83 | + |
| 84 | +In the `src/routes` directory, create an `api` folder. In that folder, create a |
| 85 | +`data.json`, which will contain the hard coded dinosaur data. |
| 86 | + |
| 87 | +Copy and paste |
| 88 | +[this json file](https://github.com/denoland/tutorial-with-svelte/blob/main/src/routes/api/data.json) |
| 89 | +into the `src/routes/api/data.json` file. (If you were building a real app, you |
| 90 | +would probably fetch this data from a database or an external API.) |
| 91 | + |
| 92 | +We're going to build out some API routes that return dinosaur information. |
| 93 | +SvelteKit provides a simple way to create API endpoints using server files. |
| 94 | + |
| 95 | +Create `src/routes/api/dinosaurs/+server.ts` to handle the `/api/dinosaurs` |
| 96 | +endpoint. This will return all dinosaurs: |
| 97 | + |
| 98 | +```js title="src/routes/api/dinosaurs/+server.ts" |
| 99 | +import { json } from "@sveltejs/kit"; |
| 100 | +import data from "../data.json" with { type: "json" }; |
| 101 | + |
| 102 | +export function GET() { |
| 103 | + return json(data); |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +Then create `src/routes/api/dinosaurs/[id]/+server.ts` to handle individual |
| 108 | +dinosaur requests at `/api/dinosaurs/:id`: |
| 109 | + |
| 110 | +```ts title="src/routes/api/dinosaurs/[id]/+server.ts" |
| 111 | +import { json } from "@sveltejs/kit"; |
| 112 | +import type { RequestHandler } from "./$types"; |
| 113 | +import data from "../../data.json" with { type: "json" }; |
| 114 | + |
| 115 | +export const GET: RequestHandler = ({ params }) => { |
| 116 | + const dinosaur = data.find((item) => { |
| 117 | + return item.name.toLowerCase() === params.id.toLowerCase(); |
| 118 | + }); |
| 119 | + |
| 120 | + if (dinosaur) { |
| 121 | + return json(dinosaur); |
| 122 | + } |
| 123 | + |
| 124 | + return json({ error: "Not found" }, { status: 404 }); |
| 125 | +}; |
| 126 | +``` |
| 127 | + |
| 128 | +SvelteKit automatically handles routing based on the file structure. The |
| 129 | +`+server.ts` files define API endpoints, and the `[id]` folder creates a dynamic |
| 130 | +route parameter. |
| 131 | + |
| 132 | +## Build the frontend |
| 133 | + |
| 134 | +### File-based routing and data loading |
| 135 | + |
| 136 | +SvelteKit uses file-based routing, where the structure of your `src/routes` |
| 137 | +directory determines your app's routes. Unlike Vue Router, you don't need to |
| 138 | +configure routes manually - SvelteKit automatically creates routes based on your |
| 139 | +file structure. |
| 140 | + |
| 141 | +In SvelteKit, `+page.svelte` files define page components, and `+page.ts` files |
| 142 | +define data loading functions that run before the page loads. This provides |
| 143 | +built-in server-side rendering and data fetching capabilities. |
| 144 | + |
| 145 | +### The pages and components |
| 146 | + |
| 147 | +SvelteKit organizes the frontend into pages and components. Pages are defined by |
| 148 | +`+page.svelte` files in the routes directory, while components can be reusable |
| 149 | +pieces of code stored anywhere in your project. |
| 150 | + |
| 151 | +Each Svelte component file contains three optional sections: `<script>`, |
| 152 | +`<template>` (the HTML), and `<style>`. The `<script>` tag contains the |
| 153 | +JavaScript/TypeScript logic, the template contains the HTML markup, and the |
| 154 | +`<style>` tag contains scoped CSS. |
| 155 | + |
| 156 | +We'll create pages for the home page and individual dinosaur details, along with |
| 157 | +data loading functions to fetch dinosaur information from our API. |
| 158 | + |
| 159 | +#### The home page |
| 160 | + |
| 161 | +The home page will display a list of dinosaurs fetched from our API. First, |
| 162 | +create `src/routes/+page.ts` to load the dinosaur data: |
| 163 | + |
| 164 | +```ts title="src/routes/+page.ts" |
| 165 | +import type { PageLoad } from "./$types"; |
| 166 | + |
| 167 | +export const load: PageLoad = async ({ fetch }) => { |
| 168 | + const res = await fetch(`/api/dinosaurs`); |
| 169 | + const dinosaurs = await res.json(); |
| 170 | + |
| 171 | + return { dinosaurs }; |
| 172 | +}; |
| 173 | +``` |
| 174 | + |
| 175 | +This load function runs on both the server and client, and the data is passed to |
| 176 | +the page component. The `fetch` function is provided by SvelteKit and works in |
| 177 | +both server and client environments. |
| 178 | + |
| 179 | +Next, update `src/routes/+page.svelte` to display the dinosaur list: |
| 180 | + |
| 181 | +```html title="src/routes/+page.svelte" |
| 182 | +<script lang="ts"> |
| 183 | + import type { PageProps } from "./$types"; |
| 184 | +
|
| 185 | + let { data }: PageProps = $props(); |
| 186 | + let dinosaurs = data.dinosaurs; |
| 187 | +</script> |
| 188 | + |
| 189 | +<main> |
| 190 | + <h1>🦕 Dinosaur app</h1> |
| 191 | + <p>Click on a dinosaur below to learn more.</p> |
| 192 | + {#each dinosaurs as dinosaur (dinosaur.name)} |
| 193 | + <a href="/{dinosaur.name.toLowerCase()}" class="dinosaur"> |
| 194 | + {dinosaur.name} |
| 195 | + </a> |
| 196 | + {/each} |
| 197 | +</main> |
| 198 | +``` |
| 199 | + |
| 200 | +This code uses Svelte's [each block](https://svelte.dev/docs/logic-blocks#each) |
| 201 | +to iterate over the `dinosaurs` array and render each dinosaur as a link. The |
| 202 | +`{#each}` block is Svelte's way of rendering lists, and the `(dinosaur.name)` |
| 203 | +provides a unique key for each item. |
| 204 | + |
| 205 | +#### The Dinosaur detail page |
| 206 | + |
| 207 | +The dinosaur detail page will display information about a specific dinosaur. |
| 208 | +SvelteKit uses folder names in square brackets to create dynamic routes. The |
| 209 | +`[dinosaur]` folder creates a route that captures the dinosaur name from the |
| 210 | +URL. |
| 211 | + |
| 212 | +First, create `src/routes/[dinosaur]/+page.ts` to load individual dinosaur data: |
| 213 | + |
| 214 | +```ts title="src/routes/[dinosaur]/+page.ts" |
| 215 | +import type { PageLoad } from "./$types"; |
| 216 | +import { error } from "@sveltejs/kit"; |
| 217 | + |
| 218 | +export const load: PageLoad = async ({ fetch, params }) => { |
| 219 | + const res = await fetch(`/api/dinosaurs/${params.dinosaur}`); |
| 220 | + const dinosaur = await res.json() as { name: string; description: string }; |
| 221 | + |
| 222 | + if (res.status === 404) { |
| 223 | + return error(404, "No dinosaur found"); |
| 224 | + } |
| 225 | + |
| 226 | + return { dinosaur }; |
| 227 | +}; |
| 228 | +``` |
| 229 | + |
| 230 | +This load function uses the `params` object to access the `dinosaur` parameter |
| 231 | +from the URL. If the API returns a 404, we use SvelteKit's `error` function to |
| 232 | +throw a 404 error. |
| 233 | + |
| 234 | +Next, create `src/routes/[dinosaur]/+page.svelte` to display the dinosaur |
| 235 | +details: |
| 236 | + |
| 237 | +```html title="src/routes/[dinosaur]/+page.svelte" |
| 238 | +<script lang="ts"> |
| 239 | + import type { PageProps } from "./$types"; |
| 240 | +
|
| 241 | + let { data }: PageProps = $props(); |
| 242 | + let dinosaur = data.dinosaur; |
| 243 | +</script> |
| 244 | + |
| 245 | +<div> |
| 246 | + <h1>{dinosaur.name}</h1> |
| 247 | + <p>{dinosaur.description}</p> |
| 248 | + <a href="/">🠠 Back to all dinosaurs</a> |
| 249 | +</div> |
| 250 | +``` |
| 251 | + |
| 252 | +This page displays the dinosaur's name and description, along with a link back |
| 253 | +to the home page. The data comes from the load function and is automatically |
| 254 | +available in the component. |
| 255 | + |
| 256 | +## Run the app |
| 257 | + |
| 258 | +Now that we've set up the frontend and backend API routes, we can run the app. |
| 259 | +In your terminal, run the following command: |
| 260 | + |
| 261 | +```shell |
| 262 | +deno task dev |
| 263 | +``` |
| 264 | + |
| 265 | +This will start the SvelteKit development server with Vite. SvelteKit |
| 266 | +automatically handles both the frontend pages and the API routes we created, so |
| 267 | +you don't need to run separate servers. |
| 268 | + |
| 269 | +Visit `http://localhost:5173` in your browser to see the app. Click on a |
| 270 | +dinosaur to see more details! |
| 271 | + |
| 272 | +You can see a live version of the app on |
| 273 | +[Deno Deploy](https://tutorial-with-svelte.deno.deno.net/). |
| 274 | + |
| 275 | +## Build and deploy |
| 276 | + |
| 277 | +SvelteKit comes with built-in build capabilities. We configured it to use the |
| 278 | +Deno adapter, which optimizes the build for deployment on Deno-compatible |
| 279 | +platforms. Run the following command to build the app in production mode: |
| 280 | + |
| 281 | +```sh |
| 282 | +deno task build |
| 283 | +``` |
| 284 | + |
| 285 | +This will: |
| 286 | + |
| 287 | +1. Build the SvelteKit app using Vite |
| 288 | +2. Generate optimized production assets |
| 289 | +3. Create server-side code compatible with Deno |
| 290 | + |
| 291 | +The built app will be ready for deployment on platforms that support Deno, such |
| 292 | +as Deno Deploy. |
| 293 | + |
| 294 | +You can deploy this app to your favorite cloud provider. We recommend using |
| 295 | +[Deno Deploy](https://deno.com/deploy) for a simple and easy deployment |
| 296 | +experience. You can deploy your app directly from GitHub, simply create a GitHub |
| 297 | +repository and push your code there, then connect it to Deno Deploy. |
| 298 | + |
| 299 | +### Create a GitHub repository |
| 300 | + |
| 301 | +[Create a new GitHub repository](https://github.com/new), then initialize and |
| 302 | +push your app to GitHub: |
| 303 | + |
| 304 | +```sh |
| 305 | +git init -b main |
| 306 | +git remote add origin https://github.com/<your_github_username>/<your_repo_name>.git |
| 307 | +git add . |
| 308 | +git commit -am 'my svelte app' |
| 309 | +git push -u origin main |
| 310 | +``` |
| 311 | + |
| 312 | +### Deploy to Deno Deploy |
| 313 | + |
| 314 | +Once your app is on GitHub, you can deploy it on the Deno Deploy<sup>EA</sup> |
| 315 | +dashboard. |
| 316 | +<a href="https://app.deno.com/" class="docs-cta deploy-cta deploy-button">Deploy |
| 317 | +my app</a> |
| 318 | + |
| 319 | +For a walkthrough of deploying your app, check out the |
| 320 | +[Deno Deploy tutorial](/examples/deno_deploy_tutorial/). |
| 321 | + |
| 322 | +🦕 Now that you can run a SvelteKit app in Deno with the Deno adapter you're |
| 323 | +ready to build real world applications! |
0 commit comments