Recently, I learned about Drizzle, a new ORM for TypeScript. It occurred to me that it would be interesting to try it out with SvelteKit to build a fullstack application entirely in TypeScript.
This article will just cover the basics of setting up a project with SvelteKit and Drizzle, but here is one of my project (WIP) built with this stack: review-planner.
As a simple example, this post will cover how to implement a user registration and login workflow with JWT authentication in a SvelteKit application. The source code (with some modifications) can be found here: svelte-jwt-example.
These are the steps:
Setting up the project
Initializing the database
Setting up the development environment
Implementing a simple user login workflow
Create the project
First, create a new SvelteKit project, and install dependencies (Official tutorial).
1
2
3
npx sv create svelte-jwt
cd svelte-jwt
npm install
There should be a .env.example file in the root directory, generated during project creation. Rename it to .env.
Note
I like to use .env file directly for my development environment for simplicity, since I deploy my stuff on k8s, and the .env file content will be overwritten by environment variables defined in the deployment.
Feel free to use other profiles like .env.dev, etc.
Database
In the installation process, Drizzle automatically creates a docker compose file, which spins up a PostgreSQL database.
Additionally, there should be some npm commands defined in package.json for database operations.
This section is about creating the database schema in SvelteKit, and pushing it to the database. Refer to the Drizzle documentation for more information.
First, define the schema in SvelteKit. To do that, find the file src/lib/schema.ts and add the following:
<!-- src/routes/register/+page.svelte --><scriptlang="ts">import{goto}from"$app/navigation";asyncfunctionhandleSubmit(event: SubmitEvent){// prevent default behavior of form submission (page reload with query string params)
event.preventDefault();constform=event.target;if(form==null||!(forminstanceofHTMLFormElement)){return;}constformData=newFormData(form);// ensure passwords match
constpass=formData.get('password')asstring;constconfPass=formData.get('confirmPassword')asstring;if(pass!==confPass){alert('Passwords do not match');return;}// send a POST request to the current route
constresp=awaitfetch(window.location.pathname,{method:'POST',headers:{'Content-Type':'application/json',},body: JSON.stringify({email: formData.get('email'),name: formData.get('name'),password: formData.get('password'),}),});if(!resp.ok){alert('Failed to sign up');return;}goto('/auth/login');}</script><h1>Sign up</h1><formon:submit={handleSubmit}><labelfor="email">Email</label><inputtype="email"id="email"name="email"required/><br/><labelfor="name">Name</label><inputtype="text"id="name"name="name"required/><br/><labelfor="password">Password</label><inputtype="password"id="password"name="password"required/><br/><labelfor="confirmPassword">Confirm Password</label><inputtype="password"id="confirmPassword"name="confirmPassword"required/><br/><buttontype="submit">Sign up</button></form>
// src/routes/register/+server.ts
import{db}from"$lib/server/db";import{user}from"$lib/server/db/schema";importtype{RequestEvent}from"./$types";// request body type
typeRegistReq={email: string;name: string;password: string;}// handling POST /api/regist
exportasyncfunctionPOST({request}:RequestEvent){constbody=awaitrequest.json()asRegistReq;// TODO validation
// insert user
awaitdb.insert(user).values({email: body.email,password: body.password,name: body.name});// respond with 201 Created
returnnewResponse(null,{status: 201});}
Implement login route
This will be another HTML form and a backend endpoint in the same manner, contained in src/routes/auth/login.