This project is a todo application, as well as tutorial for SvelteKit framework.
video course: Full Stack Web Development in the Cloud
original source code: gitpod-io/full-stack-web-development
The diagram below outlines the high-level architecture and the hosting providers for the web application, API and database.
The entire course is developed using Gitpod. For each task, we use an ephemeral developer environment that we dispose of as soon as the task is completed. Environments are fully automated and we never run npm install
or npm run dev
manually. We also don't have any code, dependencies, etc installed locally.
The course leverages the following technologies
Web application
Svelte is a compiler to develop highly performant web applications with great developer experience. The application is styled with plain CSS.
API
SvelteKit is the library & application framework powered by Svelte. It provides routing, server-side rendering and also enables us to develop a web application that works if Javascript is disabled.
Prisma is the object-relational mapping (ORM) library that let's us interact with the database. Based on models we define, Prisma generates the database schema and keeps the databsae in sync with our model(s). In addition, it generates a Typescript client we import into our code so that we have type safety when we work with database objects.
Database
Postgres is our database of choice for the course. However, thanks to Prisma's support for various other databases, it is a matter of changing configuration values to leverage a different database.
Deployment
The web application and API are hosted on Vercel whereas the database lives on Railway.
# initial SvelteKit framework project
npm init svelte@next .
# choose:
# Skeleton project
# TypeScript -> Yes
# ESLint -> No
# Prettier -> No
npm install
Run Server
npm run dev
download gitpot browser extension
create .gitpot.yml
document here!
.svelte
file (filename = component name)for example:
create lib/todo-item.svelte, then in routes/index.svelte
<script>
// import that component file
import TodoItem from "$lib/todo-item.svelte";
</script>
<!-- use component -->
<TodoItem />
tag
%tag
with --head, options, window, body, self, component, or fragmenttag
> tagfor example:
given %svelte.head% in app.html, then in routes/index.svelte
<!-- override app.html (base) -->
<svelte:head>
<title>This is a title</title>
</svelte:head>
<script>
// define variables
const title = "Todos";
</script>
<!-- use variable -->
<h1>{title}</h1>
note. use :global
, when you want to apply style to all children's in that class
since form in html, only allow GET, POST action
// add to kit
methodOverride: {
allowed: ["PUT", "PATCH", "DELETE"];
}
then in html form, send POST method to an URL with _method
param [delete, put, ...]
in src/routes
.svelte
file (filename = endpoint)for example: about-us.svelte (contain some html tags) -> http://localhost:3000/about-us
index.json.ts
file under the folderdocuments for kit.svelte Endpoints, Request
for example: todos/index.json.ts -> http://localhost:3000/todos.json
.ts
file using [uid]
in its namefor example: todos/[uid].json.ts
// create Todo item
type Todo = {
created_at: Date;
text: string;
done: boolean;
};
// let todos = []; // array
let todos: Todo[] = []; // Todo array
todos.push({
created_at: new Date(),
text: data.get("text"),
done: false,
});
in index.svelte, add a ts script with context="module" (so it running only one time)
<script context="module" lang="ts">
import type { Load } from "@sveltejs/kit";
export const load = async ({ fetch }) => {
const res = await fetch("/todos.json");
if (res.ok) {
const todos = await res.json();
// props -> return to this page body (below)
return { props: { todos } };
}
const { message } = await res.json();
return {
error: new Error(message),
};
};
</script>
in the old script, edit lang to "ts", export the props from new script
<script lang="ts">
// ...
export let todos: Todo[];
</script>
looping items in the body
{#each todos as todo}
<!-- use variable -->
<TodoItem todo="{todo}" />
<!-- or (when same name) -->
<TodoItem {todo} />
{/each}
in lib/todo-item.svelte, add a ts script to get an item from other page
<script lang="ts">
export let todo: Todo;
</script>
<!-- using same as other normal variables -->
<!-- ex. {todo.text}, {todo.done}, ... -->
pls, look up git commit!
create lib/actions/form.ts
update todos/_api.ts, to check if headers accept "application/json"
in index.svelte
add import { enhance } from "$lib/actions/form"
to module script
add processNewTodoResult, processUpdatedTodoResult to another script
const processNewTodoResult = async (
res: Response,
form: HTMLFormElement
) => {
const newTodo = await res.json();
todos = [...todos, newTodo];
form.reset();
};
const processUpdatedTodoResult = async (res: Response) => {
const updatedTodo = await res.json();
todos = todos.map((t) => {
if (t.uid === updatedTodo.uid) return updatedTodo;
return t;
});
};
in new
forms, add use:enhance={{ result:processNewTodoResult }}
in update TodoItem
to
<TodoItem
{todo}
processDeletedTodoResult={() => {
todos = todos.filter((t) => t.uid !== todo.uid);
}}
{processUpdatedTodoResult}
/>
add into script
import { enhance } from "$lib/actions/form";
export let processDeletedTodoResult: (res: Response) => void;
export let processUpdatedTodoResult: (res: Response) => void;
in update text
forms, add use:enhance={{ result:processUpdatedTodoResult }}
in delete
forms, add use:enhance={{ result:processDeletedTodoResult }}
# install prisma
$ npm install -D prisma
# check database schema
$ ./node_modules/.bin/prisma^C
$ npx prisma studio
# migrate schema
$ npx prisma migrate dev
create prisma/schema.prisma
add scripts.vercel-build to package.json
$ export VERCEL=true
$ npm run build
add scripts.postbuild to package.json (copy prisma schema to .vercel_build_output/functions/node/render)
Postgres_Connection_URL
)DATABASE_URL
= Postgres_Connection_URL
from Railway