Why Typescript and Svelte are a match made in heaven
Tags: svelte, typescript, sanity
Published: 2021-04-02 21:19:59

I've begun working more with Svelte and TypeScript and I ... Well, *I think I might be in love*.

I think you will be, too, when you see how valuable (and easy) it is to pair them together! And that's exactly what we're going to do here.

We'll begin by building out a simple and static Svelte app. Then we'll add TypeScript support to the app and discuss how and why we've made it more powerful and even easier to write the code (even though it means writing *more* code). And we'll close by exploring how simple it can be to make the application dynamic, using Sanity as our data source.

Sound good? Good! Let's dig in.

## Why you should use Svelte

[Svelte](https://svelte.dev/) is an up-and-coming JavaScript-based component framework.

I know, I know. *Another one? Really?*

Hear me out for a moment. It has its place. It's like [React](https://reactjs.org/) in that it is a component-centered means of building out UIs. In fact, [Svelte often uses React](https://svelte.dev/blog/svelte-3-rethinking-reactivity) in explaining its theoretical approach. There are nuances that separate the two, with pros and cons to each. But, to me, it boils down to one main idea when differentiating Svelte from React:

Svelte introduces the ability for almost no boilerplate code, but it's also young and fairly simplistic (relatively speaking, what's going on under the hood is ... *complicated*). React, on the other hand, often requires a fair amount of boilerplate code, even for simple interactions. But it's mature, stable, and has a large community.

For that reason, I *really* like Svelte for simple use cases. But, with wider adoption and tools like [Next.js](https://nextjs.org/), React is still my go-to for larger scale or more complex application interfaces.

## Why you should also use TypeScript

[TypeScript](https://www.typescriptlang.org/) is an extension of JavaScript. You can think of it as JavaScript with a few extra features. These features are largely focused on defining the *type* and *shape* of JavaScript objects. It requires that you be declarative about the code you're writing and have an understanding of the values your functions, variables, and objects are expecting.

While it requires more code, TypeScript is a fantastic means of catching common JavaScript bugs while in development. And for just that reason, it's worth the extra characters.

TypeScript isn't *really* a language. In fact, it compiles down to regular old JavaScript. As a bonus, you have the option of choosing a particular version of JavaScript to target when compiling, so you can use updated JavaScript features, but still maintain legacy browser support (if you're into that sort of thing)!

## Why you should use Svelte and TypeScript

Svelte is, in itself, a great toolkit. Add TypeScript to it and they become a fearsome duo that may just help Svelte scale to a point at which I'd feel comfortable implementing on larger applications.

That power comes in **being able to write better code at build time**, thus running into fewer bugs in production (i.e. at runtime). How do we achieve that?

Well, let's go through a simple example with Svelte in just JavaScript at first, before we run into an issue and can see how TypeScript will help save the day!

## A Svelte + TypeScript Example

The classic [SPA](https://en.wikipedia.org/wiki/Single-page_application) example is a to-do list. But, you know what? I don't like to-do lists. They make me think about all the things I have to do, many of which I don't want to do.

So why don't we make a *To Don't List* app? That way we can list all the things we're not going to do. Ever.

We'll build it statically at first, and later use [Sanity](https://www.sanity.io/) to store our data, and [Sanity Studio](https://www.sanity.io/studio) to populate a few items. But first, let's focus on Svelte and TypeScript.

The code we'll write in this guide is a simplified version of [the seancdavis/svelte-sanity-tonotdo project on GitHub](https://github.com/seancdavis/svelte-sanity-tonotdo). There is a working demo [deployed to Vercel](https://svelte-sanity-tonotdo.vercel.app/).

## Building a static Svelte To-Don't app

Let's begin by installing Svelte and setting up a new project.

### Install Svelte

To start, run the commands specified in [Svelte's Quickstart Guide](https://svelte.dev/blog/the-easiest-way-to-get-started#2_Use_degit) (Option #2):

```
$ npx degit sveltejs/template to-dont-list
$ cd to-dont-list
$ npm install
$ npm run dev
```

After running those four commands, Svelte's development server should be running. See it in action by visiting [http://localhost:5000](http://localhost:5000/) in your browser.

![Default Svelte starter screen](https://cdn.sanity.io/images/81pocpw8/production/ecdb16b494e1e8b8304c74d6d361add7b6ea607a-1207x839.png?w=640&h=445&fit=clip&auto=format)

### Add "InactionCard" Component

Next, let's add a component so we can build our To-Don't list. I'm going to call the component InactionCard and place it at `src/components/InactionCard.svelte`.

```html
<!-- src/components/InactionCard.svelte -->
<script>
  export let title, notes;
</script>

<div class="inaction">
  <h2 class="title">{title}</h2>
  <div class="notes">{notes}</div>
</div>
```

*Note: If you want to follow along with styling, you can replace `public/globals.css` with [the full example code](https://github.com/seancdavis/svelte-sanity-tonotdo/blob/main/svelte/public/global.css), and also pull `<style>` contents from individual component, [like this one](https://github.com/seancdavis/svelte-sanity-tonotdo/blob/main/svelte/src/components/Inaction.svelte).*

### Render (Static) Inaction Cards

To bring it to life on the home page, adjust the default intro copy and render a few inactions from the main `src/App.svelte` component.

```html
<!-- src/App.svelte -->
<script>
  import InactionCard from "./components/InactionCard.svelte";
</script>

<main>
  <h1>To Don't List</h1>
  <p class="intro">Here is a list of things I'm not going to do:</p>

  <InactionCard
    title="Return VHS to Blockbuster"
    notes="You've had it since 1998. What's the rush?"
  />
  <InactionCard
    title="Fix the hole in the wall"
    notes="Keeping it where it is adds character and nostalgia."
  />
  <InactionCard
    title="Do the dishes"
    notes="Someone else will probably get to them. Just leave them in the sink."
  />
</main>

```

Take a look back at your browser and you should see the list! (Refresh if you don't.)

![Static To Don't List Svelte app](https://cdn.sanity.io/images/81pocpw8/production/38711c51bf399f110ac5d46f30db098d2df870be-1207x839.png?w=640&h=445&fit=clip&auto=format)

There we go! We have a (very simple) Svelte app with just HTML, CSS, and JavaScript.

## Adding TypeScript to a Svelte project

Now, suppose we wanted to add a due date. It's not too difficult in this example, right? Let's do it!

We add a new property to the Inaction Card component:

```html
<!-- src/components/InactionCard.svelte -->
<script>
  export let title, notes, dueDate;
</script>

<div class="inaction">
  <h2 class="title">{title}</h2>
  <span class="due-date">{dueDate}</span>
  <div class="notes">{notes}</div>
</div>
```

And to the inactions we render, we can add the new prop:

```html
<!-- src/App.svelte -->

<!-- ... -->
<InactionCard
	title="Return VHS to Blockbuster"
	dueDate={new Date("1992-02-29")}
	notes="You've had it since 1998. What's the rush?"
/>
<InactionCard
	title="Fix the hole in the wall"
	dueDate={Date.now()}
	notes="Keeping it where it is adds character and nostalgia."
/>
<InactionCard
	title="Do the dishes"
	dueDate={new Date("2024-02-29")}
	notes="Someone else will probably get to them. Just leave them in the sink."
/>

```

Then jump back over to your browser:

![To Don't List with due date attribute](https://cdn.sanity.io/images/81pocpw8/production/f3c42b6eec47e3217e8507d380279da9c5831a51-1207x839.png?w=640&h=445&fit=clip&auto=format)

Notice anything weird? I do. Two things, actually:

1. The first and the third inactions display an unnecessarily-long date. That could have been easily formatted, but we're not going to worry about that here.
2. The second date is a number. That's a bigger issue, and I'll explain why.

What if, we rendered the type of `dueDate` instead? (i.e. `typeof dueDate`)

![To Don't List with typeof dueDate field](https://cdn.sanity.io/images/81pocpw8/production/ed843dcdb840497124fb2f52e7cc7416a682c453-1207x839.png?w=640&h=445&fit=clip&auto=format)

We get `number` for the second one! Even with this small of an application that could absolutely be an issue. Suppose you wanted to better control the formatting of the date. You'd have to first introspect the type of date and then format appropriately. It'd be much easier if we know (with confidence) what type to expect.

### TypeScript Saves the Day!

We can use TypeScript to tell our application exactly what we expect in `dueDate`.

Since our Svelte app is still largely untouched, we can use the built-in utility to convert it to TypeScript. Shut down your development server (`ctrl`+`c`), and then run the following commands:

```
$ node scripts/setupTypeScript.js
$ npm install
```

This made several changes, but the most important one is that you now need to specify the language in your `<script>` tags as `ts`. Take a look at the change in `src/App.svelte`:

```html
<!-- src/App.svelte -->
<script lang="ts">
```

Make this change to your Inaction Card component.

### DRY Up Inactions

To make this adjustment process smoother, let's [DRY up](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) our code. Adjust your `App.svelte` to loop through an array of inactions that we create in the `<script>` section.

```html
<!-- src/App.svelte -->
<script lang="ts">
  import InactionCard from "./components/InactionCard.svelte";

  let inactions = [
    {
      title: "Return VHS to Blockbuster",
      dueDate: new Date("1992-02-29"),
      notes: "You've had it since 1998. What's the rush?",
    },
    {
      title: "Fix the hole in the wall",
      dueDate: Date.now(),
      notes: "Keeping it where it is adds character and nostalgia.",
    },
    {
      title: "Do the dishes",
      dueDate: new Date("2024-02-29"),
      notes:
        "Someone else will probably get to them. Just leave them in the sink.",
    },
  ];
</script>

<main>
  <h1>To Don't List</h1>
  <p class="intro">Here is a list of things I'm not going to do:</p>

  {#each inactions as inaction}
    <InactionCard
      title={inaction.title}
      dueDate={inaction.dueDate}
      notes={inaction.notes}
    />
  {/each}
</main>

```

### Define Inaction Type

TypeScript is really all about *types*. (It's in the name, after all!) To add types in a Svelte project, we'll want a separate `.ts` file. Let's put our types in a types directory, just to stay organized.

```tsx
// src/types/Inaction.ts
export interface Inaction {
  title: string;
  dueDate: Date;
  notes: string;
}
```

This tells us that when we set an object as an `Inaction`, we expect it to have `title` and `notes` as a `string`, and `dueDate` as a `Date`.

To use it, import the type and then note it. The syntax is a little goofy at first, but you'll get the hang of it.

```html
<!-- src/App.svelte -->
<script lang="ts">
  import type { Inaction } from "./types/Inaction";
  import InactionCard from "./components/InactionCard.svelte";

  // inactions should be an array of items with the Inaction type.
  let inactions: Inaction[] = [
    {
      title: "Return VHS to Blockbuster",
      dueDate: new Date("1992-02-29"),
      notes: "You've had it since 1998. What's the rush?",
    },
    {
      title: "Fix the hole in the wall",
      dueDate: Date.now(),
      notes: "Keeping it where it is adds character and nostalgia.",
    },
    {
      title: "Do the dishes",
      dueDate: new Date("2024-02-29"),
      notes:
        "Someone else will probably get to them. Just leave them in the sink.",
    },
  ];
</script>

```

After I restarted my code editor ([VS Code](https://code.visualstudio.com/)), it told me something was wrong!

![VS Code showing TypeScript error](https://cdn.sanity.io/images/81pocpw8/production/4b487ba0937b3873200138bcb4a0ddbbe5c9c119-478x167.png?w=478&h=167&fit=clip&auto=format)

This says `dueDate` on the second inaction is the wrong type!

The power in this is great! VS Code told me there's an issue with the code without having to open up the browser!

If that's useful even on this small scale, imagine how helpful it would be in a large application where `Inaction` may be used dozens or hundreds of times!

### Validating Types

Aside from the code editor adding little squiggles, nothing would actually appear wrong during the Svelte build. Fortunately, Svelte gives us a tool to make sure everything is okay, `svelte-check`.

Try running the following command:

```
$ npx svelte-check
```

I see an error and three warnings:

```
Loading svelte-check in workspace: .../to-dont-list
Getting Svelte diagnostics...
====================================

.../to-dont-list/src/App.svelte:13:7
Error: Type 'number' is not assignable to type 'Date'. (ts)
      title: "Fix the hole in the wall",
      dueDate: Date.now(),
      notes: "Keeping it where it is adds character and nostalgia.",

.../to-dont-list/src/components/InactionCard.svelte:2:14
Hint: Variable 'title' implicitly has an 'any' type, but a better type may be inferred from usage. (ts)
<script lang="ts">
  export let title, notes, dueDate;
</script>

.../to-dont-list/src/components/InactionCard.svelte:2:21
Hint: Variable 'notes' implicitly has an 'any' type, but a better type may be inferred from usage. (ts)
<script lang="ts">
  export let title, notes, dueDate;
</script>

.../to-dont-list/src/components/InactionCard.svelte:2:28
Hint: Variable 'dueDate' implicitly has an 'any' type, but a better type may be inferred from usage. (ts)
<script lang="ts">
  export let title, notes, dueDate;
</script>
```

You can wire up this command to your build process so you don't send invalid typed code to production. Amazing!

I hope this *validates* (😉) the use of TypeScript and Svelte enough to entice you to try it out for yourself! But before we go, let's take a look at wiring this up to Sanity so that we can pull in data dynamically from an external source.

## Wiring up Sanity to a Svelte project

To get Sanity up and running, begin by [installing the CLI](https://www.sanity.io/docs/getting-started-with-sanity-cli) and bootstrapping a new project.

```
$ npm install -g @sanity/cli
$ sanity init
```

After authenticating, choose the appropriate items to get your project started. These were my choices:

- Select project to use: *Create a new project*
- Your project name: *To Not Do*
- Use the default dataset configuration? *Yes*
- Project output path: (Use recommendation)
- Select project template: *Clean project with no predefined schemas*

That will install dependencies and prepare a [Studio](https://www.sanity.io/studio) project. You should now be able to change into the new directory Sanity created for you and start the Studio.

```
$ cd path/to/new/directory
$ npm start
```

By default, the Studio will be available in your browser at [http://localhost:3333/](http://localhost:3333/). You'll have to sign in again. And when you do, you should see that you have an empty schema.

![Sanity Studio with empty schema](https://cdn.sanity.io/images/81pocpw8/production/f1b7ffe4df17fbe1d4f7ecf88d823b4d2f2f4f9b-1219x927.png?rect=0,0,1218,927&w=640&h=487&fit=clip&auto=format)

### Add Data Model

Let's create our data model, which Sanity calls a *document type*. If you're not familiar with Sanity, [here's a nice overview of content modeling](https://www.sanity.io/docs/content-modelling).

To add our model, we're going to edit the (nearly) blank schema file Sanity gave us. This file lives in `schemas/schema.js`. It should look something like this:

```jsx
// First, we must import the schema creator
import createSchema from "part:@sanity/base/schema-creator";

// Then import schema types from any plugins that might expose them
import schemaTypes from "all:part:@sanity/base/schema-type";

// Then we give our schema to the builder and provide the result to Sanity
export default createSchema({
  // We name our schema
  name: "default",
  // Then proceed to concatenate our document type
  // to the ones provided by any plugins that are installed
  types: schemaTypes.concat([
    /* Your types here! */
  ]),
});
```

We're going to put our type in the `types` object. Let's create an `inaction` model with the proper fields:

```jsx
import createSchema from "part:@sanity/base/schema-creator";
import schemaTypes from "all:part:@sanity/base/schema-type";

export default createSchema({
  name: "default",
  types: schemaTypes.concat([
    {
      title: "Inaction",
      name: "inaction",
      type: "document",
      fields: [
        { title: "Title", name: "title", type: "string" },
        { title: "Notes", name: "notes", type: "text" },
        { title: "Due Date", name: "dueDate", type: "date" },
      ],
    },
  ]),
});
```

Notice that if you go back to your browser, Studio already picked up your changes! (*Science!* Or something?)

Use your new model to recreate the static content from `src/App.svelte`.

![Create Inaction objects with Sanity Studio](https://cdn.sanity.io/images/81pocpw8/production/507a150688e813aa24fd85706f19e08295bb1394-1207x839.png?w=640&h=445&fit=clip&auto=format)

### Pull in Data Dynamically

Once that content is in place, we can pull it into project. First, install Sanity's JavaScript client.

```
$ npm install @sanity/client
```

Then we can make some changes to the `<script>` in our `App.svelte` component to fetch our inactions from the Sanity API.

```html
<!-- src/App.svelte -->
<script lang="ts">
  import { onMount } from "svelte";
  import sanityClient from "@sanity/client";

  import type { Inaction } from "./types/Inaction";
  import InactionCard from "./components/InactionCard.svelte";

  // Create a client to connect to the Sanity datastore.
  const sanity = sanityClient({
    apiVersion: 'v2021-03-25',
    projectId: "...",
    dataset: "production",
    useCdn: false,
  });

  // Initialize our inactions as an empty array.
  let inactions: Inaction[] = [];

  // Fetch the inactions from Sanity, and replace the array.
  async function fetchInactions() {
    const query = '*[_type == "inaction"]{ _id, title, notes, dueDate }';
    inactions = await sanity.fetch(query);
  }

  // Run the fetch function when the component is ready (mounted).
  onMount(fetchInactions);
</script>

```

And now you are reading dynamic data for the list of things you're never going to have to do!

## Next Steps

I hope you are intrigued enough by this to to tinker with these super cool tools! And if you want to keep going with this example, here are some ideas on where to go next:

- Add a form to submit new Inaction objects to Sanity. (The [demo](https://svelte-sanity-tonotdo-seancdavis.vercel.app/) and [its code](https://github.com/seancdavis/svelte-sanity-tonotdo) have a working example.) *Note: If you go this route, you'll want to start thinking about [authenticating your API requests](https://www.sanity.io/docs/http-auth).*
- Deploy the project to a build and hosting service, like [Vercel](https://vercel.com/).
- [Deploy Sanity Studio](https://www.sanity.io/docs/deployment) so you can make edits in Sanity, without having to run the Studio locally.

I'd love to learn where you choose to take your project. The things we can build with Svelte, TypeScript, and Sanity are endless! [Let's chat!](https://twitter.com/seancdavis29)

Last, here are a few other resources for further reading on Svelte + TypeScript:

- [Svelte and TypeScript - Together at last!](https://blog.scottlogic.com/2020/07/24/svelte-ts.html) (Scott Logic)
- [TypeScript support in Svelte](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript) (MDN)
- [Integrating TypeScript with Svelte](https://css-tricks.com/integrating-typescript-with-svelte/) (CSS-Tricks)
- [Svelte <3 TypeScript](https://svelte.dev/blog/svelte-and-typescript) (Svelte)