Skip to content

Introducing the All New SvelteKit and SCSS Kit for starter.dev

Introduction

At This Dot Labs, we love Svelte. We've even created a starter.dev kit for SvelteKit that you can use to scaffold your next frontend project using SCSS, TypeScript, Vitest and Storybook.

What is starter.dev?

Starter.dev helps developers get started building web apps with a variety of frameworks, showcasing how various libraries can fit together to solve similar problems. To do that, This Dot Labs has built a series of showcase apps that recreate the experience of using GitHub.

What is SvelteKit? How is it unique?

SvelteKit is a full-stack framework that gives you the best of both worlds: the page is server-side rendered on your first visit, but when you navigate to other pages, they are client-side rendered. SvelteKit gives you levers for your pages to use SSR (server-side rendering), CSR (client-side rendering), SSG (static site generation), SPA (single page application) & MPA (multi page application). The core of SvelteKit provides a highly configurable rendering engine.

Why SvelteKit and not Svelte?

SvelteKit isn’t built on top of Svelte, but it’s a backend web framework where Svelte is used as the view layer. In theory, you could rip it out and replace it with another component framework that supports server-side rendering, and the same is true for other web frameworks.

This allows us to deploy everything as a Node server, or even use Vercel and serverless functions.

Other reasons to use SvelteKit include:

  1. Pages (file based routing)
  2. Endpoints (API routes)
  3. Nested layouts (way more powerful than just nesting files because the segment of the URL maps to your component hierarchy)
  4. Hot module replacement (instant updates in the browser when you make a change preserving application state)
  5. Preprocessing (TypeScript, SCSS, and Pug among others)
  6. Building component libraries (creating and publishing npm packages)
  7. Deployment options (adapters for any platform)

Building a SvelteKit showcase presented several challenges given its uniqueness and different approach to building web apps. This blog post details what we chose to include in our SvelteKit GitHub clone, and how we integrated them.

Project Structures and Naming Conventions

SvelteKit-SCSS Project Structure

SvelteKit has unique conventions in its project structure and naming conventions.

Project Files

src

This is the meat of the project:

  • lib contains your library code, which can be imported via the $lib alias, or packaged up for distribution using svelte-package. It can be imported by using the $lib/* alias.
    • server contains your server-only library code. SvelteKit will prevent you from importing these in client code.
    • components contain single responsibility components that are imported in our routes. They typically contain three files: .spec.ts for unit tests, .svelte and .stories.ts for storybook stories. Barrel files were not necessary here.
    • images contains images that can be imported, and used in the project.
    • stores contains state that needs to be accessed by multiple unrelated components, or by a regular JS module.
    • styles contains styles that can be imported in our Svelte applications.
  • params contains any param matchers your app needs
  • routes contains the routes of your application
  • app.html is your page template — an HTML document containing the following placeholders:
    • %sveltekit.head% — link and script elements needed by the app, plus any head content
    • %sveltekit.body% — the markup for a rendered page. This should live inside a <div> or other element, rather than directly inside the body element, to prevent bugs caused by browser extensions injecting elements that are then destroyed by the hydration process. SvelteKit will warn you in development if this is not the case
    • %sveltekit.assets% — either paths.assets, if specified, or a relative path to paths.base
    • %sveltekit.nonce% — a CSP nonce for manually included links and scripts, if used
  • hooks.server.ts contains your application's hooks

static

Any static assets that should be served as-is, like robots.txt or favicon.png, go in here.

svelte.config.js

This file contains your Svelte and SvelteKit configuration.

tsconfig.json

Since SvelteKit relies on certain configuration being set a specific way, it generates its own .svelte-kit/tsconfig.json file, which your own config extends.

vite.config.js

A SvelteKit project is really just a Vite project that uses the @sveltejs/kit/vite plugin, along with any other Vite configuration.

Routing

To get a deep dive of how routing works with SvelteKit, please check out this article.

SCSS

SvelteKit supports a number of CSS preprocessors.

For people who are new to Svelte or SvelteKit, the syntax for using SCSS or SASS is simple, just need to add the lang="sass" attribute to the style tag.

    <style lang="scss">
        ul {
            list-style-type: circle;
            li {
                position: relative;
            }
        }
    </style>
    npm i -D sass

Then add SCSS support with the svelte-preprocess package.

// svelte.config.js

import adapter from "@sveltejs/adapter-auto";
import preprocess from "svelte-preprocess";

/** @type {import('@sveltejs/kit').Config} */
const config = {
  // Consult https://github.com/sveltejs/svelte-preprocess
  // for more information about preprocessors
  preprocess: preprocess({ typescript: true, scss: true }),

  kit: {
    adapter: adapter(),
  },
};

export default config;

Why SCSS and not Tailwind?

Tailwind CSS has been used by several other starter kits. We otherwise decided to go with SCSS as the syntax is simple and easily understood by even beginners.

Vitest

Test Driven Development (TDD) is one of the best ways to ensure your code works like it's supposed to work. It can also help you create reliable builds during continuous deployments.

Vitest is an up-and-coming testing framework which has similar functionality to Jest.

Since we are using Vite as our build tool for Svelte in this kit, Vitest has very good integration with Vite, and offers a similar testing environment without needing extra configuration.

To test Svelte components that seemed to be hard to test. Such as two-way bindings, name slots, Context API, etc., we need to add more configuration.

We added @testing-library/svelte, jsdom and @testing-library/jest-dom that allow for similar functionality as Jest.

npm i -D @testing-library/svelte @testing-library/jest-dom jsdom

We ensured the $lib alias is supported in our tests by resolving the alias in our vite.config.ts. We also added a setupTest.ts to add @testing-library/jest-dom matchers & mocks of SvelteKit modules.

// vite.config.ts

import { sveltekit } from "@sveltejs/kit/vite";
import type { UserConfig } from "vite";
import { configDefaults, type UserConfig as VitestConfig } from "vitest/config";
import path from "path";

const config: UserConfig & { test: VitestConfig["test"] } = {
  plugins: [sveltekit()],
  define: {
    // Eliminate in-source test code
    "import.meta.vitest": "undefined",
  },
  test: {
    // jest like globals
    globals: true,
    environment: "jsdom",
    // in-source testing
    includeSource: ["src/**/*.{js,ts,svelte}"],
    // Add @testing-library/jest-dom matchers & mocks of SvelteKit modules
    setupFiles: ["./setupTests.ts"],
    // Exclude files in c8
    coverage: {
      exclude: ["setupTest.ts"],
    },
    deps: {
      // Put Svelte component here, e.g., inline: [/svelte-multiselect/, /msw/]
      inline: [],
    },
    // Exclude playwright tests folder
    exclude: [...configDefaults.exclude, "tests"],
  },

  // Ensure the $lib alias is supported in our tests
  resolve: {
    alias: {
      $lib: path.resolve(__dirname, "./src/lib"),
    },
  },
};

export default config;
// setupTest.ts

/* eslint-disable @typescript-eslint/no-empty-function */
import matchers from "@testing-library/jest-dom/matchers";
import { expect, vi } from "vitest";
import type { Navigation, Page } from "@sveltejs/kit";
import { readable } from "svelte/store";
import * as environment from "$app/environment";
import * as navigation from "$app/navigation";
import * as stores from "$app/stores";

// Add custom jest matchers
expect.extend(matchers);

// Mock SvelteKit runtime module $app/environment
vi.mock("$app/environment", (): typeof environment => ({
  browser: false,
  dev: true,
  building: false,
  version: "any",
}));

// Mock SvelteKit runtime module $app/navigation
vi.mock("$app/navigation", (): typeof navigation => ({
  afterNavigate: () => {},
  beforeNavigate: () => {},
  disableScrollHandling: () => {},
  goto: () => Promise.resolve(),
  invalidate: () => Promise.resolve(),
  invalidateAll: () => Promise.resolve(),
  preloadData: () => Promise.resolve(),
  preloadCode: () => Promise.resolve(),
}));

// Mock SvelteKit runtime module $app/stores
vi.mock("$app/stores", (): typeof stores => {
  const getStores: typeof stores.getStores = () => {
    const navigating = readable<Navigation | null>(null);
    const page = readable<Page>({
      url: new URL("http://localhost"),
      params: {},
      route: {
        id: null,
      },
      status: 200,
      error: null,
      data: {},
      form: undefined,
    });
    const updated = {
      subscribe: readable(false).subscribe,
      check: () => false,
    };

    return { navigating, page, updated };
  };

  const page: typeof stores.page = {
    subscribe(fn) {
      return getStores().page.subscribe(fn);
    },
  };
  const navigating: typeof stores.navigating = {
    subscribe(fn) {
      return getStores().navigating.subscribe(fn);
    },
  };
  const updated: typeof stores.updated = {
    subscribe(fn) {
      return getStores().updated.subscribe(fn);
    },
    check: () => false,
  };

  return {
    getStores,
    navigating,
    page,
    updated,
  };
});

If you want to see some test recipes you can use on your SvelteKit projects, check out our SvelteKit-SCSS Github showcase.

Storybook

Like many of the other starter.dev kits, the SvelteKit starter uses Storybook to interactively view and build components in isolation.

For more information on Storybook and SvelteKit visit the article.

Linting

ESLint and Prettier are useful tools for keeping the project neat and consistent among multiple contributors.

To quickly format your project, run:

    npm run format

Running ESLint and Prettier as part of your git workflow is important because it helps you fail fast. This helps us, as contributors, to have a more consistent production codebase.

We achieved this in our SvelteKit-SCSS Github showcase with the help of Husky, lint-staged and Prettier.

How does lint-staged work? It's specifically designed to work on "staged" files, which are files you've changed or created, but haven't yet committed to your project. Working on staged files limits the number of files you need to lint at any given time, and makes the workflow faster.

We configured lint-staged in our package.json. This runs Prettier pre-commit, and ensures the code is up to our ESLint standards.

    // package.json
    "lint-staged": {
        "src/**/*{.js,.ts,.html,.css,.svelte}": [
            "prettier --plugin-search-dir . --write ."
        ],
        "src/**/*{.js,.ts,.svelte}": "eslint ."
    },

The commands you configure will run "pre-commit". As you're attempting to commit files to your project you'll see ESLint run in your terminal. Once it's done you may have successfully committed, or find yourself with linting errors you need to fix before you're able to commit the code.

This works hand-in-hand with husky. Husky uses distinct bash files with filenames that match the workflow step they correspond to, e.g. "pre-commit".

    #!/bin/sh
    . "$(dirname "$0")/_/husky.sh"

    cd svelte-kit-scss
    npx lint-staged

Running ESLint, or Prettier as part of your git workflow is important because it helps you fail fast, which helps contributors have a more consistent production codebase.

Conclusion

SvelteKit is a relatively new and novel framework. The structure represents our best judgment of how a basic SvelteKit application should be. We welcome everyone to take a look, and contribute back to the SvelteKit-SCSS and our SvelteKit-SCSS Github showcase if you have any improvements that you would like to propose!

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

State of Svelte Wrap-up cover image

State of Svelte Wrap-up

In this State of Svelte event, our panelists discussed updates, LTS releases, and APIs, with Node.js maintainers, technical steering committee members, and collaborators. In this wrap-up, we will take a deeper look into these latest developments, and explore what is on the horizon for Svelte. You can watch the full State of Svelte event on the This Dot Media YouTube Channel. Here is a complete list of the host and panelists that participated in this online event. Hosts:__ - Tracy Lee, CEO, This Dot Labs, @ladyleet - Rob Ocel, Team Lead & Software Architect, @robocell Panelists:__ - Scott Spence, Svelte Society, Developer Relations Engineer at Storyblok, @spences10 - Brittney Postma, Founder Svelte Sirens & Software Engineer Design Systems at Provi, @BrittneyPostma - Geoff Rich, Senior Software Engineer, Ordergroove | Svelte Core Team, @geoffrich - Simon Holthausen, Software Engineer at Vercel | Full-time Svelte maintainer, @dummdidumm - Kevin Åberg Kultalahti, Co-founder & Technical Community Builder at Svelte Society, Main Organizer at Svelte Summit, @kevmodrome Svelte 4 The chat got off to a great start with a discussion about Svelte 4, and what we can expect with that release. Simon spoke about how it will be more of a maintenance update than anything else. This version of Svelte will raise the minimum required Node version and use newer versions of Typescript as well. There will also be other minor breaking changes, but the release will mainly be focused on internally updating the repository by converting it to a mono-repo. As soon as these updates are done, Simon said they will immediately begin work on Svelte 5. Typescript and Svelte Scott brings up the reasons for not using Typescript in Svelte. Simon said a decision was made to transition the Svelte repository from using Typescript to using Javascript. There were questions about why types and type safety were being taken away from the repository. Simon clarified that the repository will be getting rid of .TS files, but they are not getting rid of type checking with Typescript, and the code will still be fully typed checked at the same level as before. The plan is to do it through JS Docs. JS Docs provides the same level of type safety you get through Typescript, but there is no longer a need for a compile step when using JS Docs. There is also no need to ship any Source Maps, and it should be easier to debug. Kevin also wanted to be clear that Typescript can still be used when building a Svelte app. Why Svelte? Rob notes that the official release of Svelte happened about 4 months ago, and asks the panelists how the launch has been going so far. Kevin goes first, talking about how everyone with whom he has talked about it has been very excited about it. He talks about how the form actions and data loading are very popular. In other frameworks, you have to attach event listeners, and then do the fetching on clients. Svelte simplifies all of that, and allows you to get rid of a lot of code by using the features in Svelte. Svelte REPL Kevin talks about the Svelte REPL, and how it plays into why Svelte is getting so big. Svelte isn’t just easy, it’s the fact that it is social in the fact that you can share a REPL and show someone how to do something with Svelte. If you have an issue, you can usually find a solution in the Svelte REPL. Server and Client Geoff talks about this aspect of Svelte. He says that they were treated as two separate entities, and there was talk about how to make them more interconnected so that it’s easier to use the server data, and get it into components. In SvelteKit, you have a load function in a separate file that defines how that data is loaded. Svelte also calls a JSON endpoint and then that component in the JSON data. State Management Geoff brings up the simple state management model that Svelte has, and they really don’t want to give that up by implementing too many things like short syntax. Simon adds that there is no real reason to bloat the syntax in the Svelte files. He doesn’t want the interoperability that Svelte currently offers. Signals vs Store A question is brought up about Signals vs Store, and if they are the same. Simon talks more about how they are related, but they are not necessarily the same. He explains that the API for Store is a little more settled right now where the API for Signals is a little more in exploration. Usability is also different because Signals is more primitive, and everything is composed of functions which you call in a certain way. With Stores, you wrap a store and map the values that are pushed into something different. How the panelists found out about Svelte The latter part of this event focused on how each panelist found Svelte and got involved with it. It was a very interesting part of the conversation to hear the backgrounds of each panelist, and why they got more and more involved with Svelte and everything that was going with it. It went very in depth, and would be worth exploring more by watching the conversation unfold on the event video. Conclusion The panelists were very engaged, and there was a lot of dialogue about Svelte and the exciting things being done. The panelists also finished by bringing up ways to get involved with the Svelte community. You can watch the full State of Svelte event on the This Dot Media Youtube Channel....

Harnessing the Power of Threlte - Building Reactive Three.js Scenes in Svelte cover image

Harnessing the Power of Threlte - Building Reactive Three.js Scenes in Svelte

Introduction Web development has evolved to include immersive 3D experiences through libraries like Three.js. This powerful JavaScript library enables the creation of captivating 3D scenes within browsers. Three.js: The 3D Powerhouse Three.js democratizes 3D rendering, allowing developers of all skill levels to craft interactive 3D worlds. Svelte Ecosystem: Svelte Cubed and Svelthree The Svelte ecosystem presents solutions like Svelte Cubed and Svelthree, which bridges Svelte with Three.js, offering streamlined reactivity for 3D web experiences. Introducing Threlte v6: Uniting SvelteKit 1.0, Svelte 4, and TypeScript Threlte v6 is a rendering and component library for Svelte that seamlessly integrates Three.js. By harnessing TypeScript's types, it provides a robust and delightful coding experience. In this tutorial, we'll showcase Threlte's capabilities by building an engaging website header: an auto-rotating sphere that changes color on mouse down. Using Threlte v6, SvelteKit 1.0, and Three.js, we're set to create a visually stunning experience. Let's dive in! Setting up Threlte Before building our scene, we need to set up Threlte. We can scaffold a new project using the CLI or manually install Threlte in an existing project. Option 1: Scaffold a New Threlte Project Create a new SvelteKit project and install Threlte with: `bash npm create threlte my-project ` Option 2: Manual Installation For an existing project, select the necessary Threlte packages and install: `bash npm install three @threlte/core @threlte/extras @threlte/rapier @dimforge/rapier3d-compat @threlte/theatre @theatre/core @theatre/studio @types/three ` Configuration adjustments for SvelteKit can be made in the "vite.config.js" file: `js const config = { // ... ssr: { noExternal: ['three'] } }; ` With Threlte configured, we're ready to build our interactive sphere. In the next chapter, we'll lay the groundwork for our exciting 3D web experience! Exploring the Boilerplate of Threlte Upon scaffolding a new project using npm create threlte`, a few essential boilerplate files are generated. In this chapter, we'll examine the code snippets from three of these files: `lib/components/scene.svelte`, `routes/+page.svelte`, and `lib/components/app.svelte`. 1. lib/components/scene.svelte`: This file lays the foundation for our 3D scene. Here's a brief breakdown of its main elements: - Perspective Camera**: Sets up the camera view with a specific field of view and position, and integrates `OrbitControls` for auto-rotation and zoom management. - Directional and Ambient Lights**: Defines the lighting conditions to illuminate the scene. - Grid**: A grid structure to represent the ground. - ContactShadows**: Adds shadow effects to enhance realism. - Float**: Wraps around 3D mesh objects and defines floating properties, including intensity and range. Various geometrical shapes like BoxGeometry, TorusKnotGeometry, and IcosahedronGeometry are included here. 2. routes/+page.svelte`: This file handles the ui of the index page and imports all necessary components we need to bring our vibrant design to life. 3. lib/components/app.svelte`: This file is where you would typically define the main application layout, including styling and embedding other components. Heading to the Fun Stuff With the boilerplate components explained, we're now ready to dive into the exciting part of building our interactive 3D web experience. In the next section, we'll begin crafting our auto-rotating sphere, and explore how Threlte's robust features will help us bring it to life. Creating a Rotating Sphere Scene In this chapter, we'll walk you through creating an interactive 3D sphere scene using Threlte. We'll cover setting up the scene, the sphere, the camera and lights, and finally the interactivity that includes a scaling effect and color changes. 1. Setting Up the Scene First, we need to import the required components and utilities from Threlte. `typescript import { T } from '@threlte/core'; import { OrbitControls } from '@threlte/extras'; ` 2. Setting Up the Sphere We'll create the 3D sphere using Threlte's ` and `` components. `svelte ` 1. `: This is a component from Threlte that represents a 3D object, which in this case is a sphere. It's the container that holds the geometry and material of the sphere. 2. `: This is the geometry of the sphere. It defines the shape and characteristics of the sphere. The `args` attribute specifies the parameters for the sphere's creation: - The first argument (1`) is the radius of the sphere. - The second argument (32`) represents the number of width segments. - The third argument (32`) represents the number of height segments. 3. `: This is the material applied to the sphere. It determines how the surface of the sphere interacts with light. The `color` attribute specifies the color of the material. In this case, the color is dynamic and defined by the `sphereColor` variable, which updates based on user interaction. The `roughness` attribute controls the surface roughness of the sphere, affecting how it reflects light. 3. Setting Up the Camera and Lights Next, we'll position the camera and add lights to create a visually appealing scene. `svelte ` 1. `: This component represents the camera in the scene. It provides the viewpoint through which the user sees the 3D objects. The `position` attribute defines the camera's position in 3D space. In this case, the camera is positioned at `(-10, 20, 10)`. The `fov` attribute specifies the field of view, which affects how wide the camera's view is. - makeDefault`: This attribute makes this camera the default camera for rendering the scene. 2. `: This component provides controls for easy navigation and interaction with the scene. It allows the user to pan, zoom, and orbit around the objects in the scene. The attributes within the `` component configure its behavior: - enableZoom`: Disables zooming using the mouse scroll wheel. - enablePan`: Disables panning the scene. - enableDamping`: Enables a damping effect that smoothens the camera's movement. - autoRotate`: Enables automatic rotation of the camera around the scene. - autoRotateSpeed`: Defines the speed of the auto-rotation. 3. `: This component represents a directional light source in the scene. It simulates light coming from a specific direction. The attributes within the `` component configure the light's behavior: - intensity`: Specifies the intensity of the light. - position.x` and `position.y`: Define the position of the light source in the scene. 4. `: This component represents an ambient light source in the scene. It provides even lighting across all objects in the scene. The `intensity` attribute controls the strength of the ambient light. 4. Interactivity: Scaling and Color Changes Now we'll add interactivity to the sphere, allowing it to scale and change color in response to user input. First, we'll import the required utilities for animation and set up a spring object to manage the scale. `typescript import { spring } from 'svelte/motion'; import { onMount } from 'svelte'; import { interactivity } from '@threlte/extras'; interactivity(); const scale = spring(0, { stiffness: 0.1 }); onMount(() => { scale.set(1); }); ` We'll update the sphere definition to include scaling: `svelte scale.set(1.1)} on:pointerleave={() => scale.set(1)}> ` Lastly, we'll add code to update the color of the sphere based on the mouse's position within the window. `typescript let mousedown = false; let rgb: number[] = []; function updateSphereColor(e: MouseEvent) { if (mousedown) { rgb = [ Math.floor((e.pageX / window.innerWidth) 255), Math.floor((e.pageY / window.innerHeight) 255), 150 ]; } } window.addEventListener('mousedown', () => (mousedown = true)); window.addEventListener('mouseup', () => (mousedown = false)); window.addEventListener('mousemove', updateSphereColor); $: sphereColor = rgb.join(','); ` We have successfully created a rotating sphere scene with scaling and color-changing interactivity. By leveraging Threlte's capabilities, we have built a visually engaging 3D experience that responds to user input, providing a dynamic and immersive interface. Adding Navigation and Scroll Prompt in `app.svelte` In this chapter, we'll add a navigation bar and a scroll prompt to our scene. The navigation bar provides links for user navigation, while the scroll prompt encourages the user to interact with the content. Here's a step-by-step breakdown of the code: 1. Importing the Canvas and Scene The Canvas` component from Threlte serves as the container for our 3D scene. We import our custom `Scene` component to render within the canvas. `typescript import { Canvas } from '@threlte/core'; import Scene from './Scene.svelte'; ` 2. Embedding the 3D Scene The Canvas` component wraps the `Scene` component to render the 3D content. It is positioned absolutely to cover the full viewport, and the `z-index` property ensures that it's layered behind the navigation elements. `svelte ` 3. Adding the Navigation Bar We use a ` element to create a horizontal navigation bar at the top of the page. It contains a home link and two navigation list items. The styling properties ensure that the navigation bar is visually appealing and positioned correctly. `svelte Home Explore Learn ` 4. Adding the Scroll Prompt We include a "Give a scroll" prompt with an ` element to encourage user interaction. It's positioned near the bottom of the viewport and styled for readability against the background. `svelte Give a scroll ` 5. Styling the Components Finally, the provided CSS styles control the positioning and appearance of the canvas, navigation bar, and scroll prompt. The CSS classes apply appropriate color, font, and layout properties to create a cohesive and attractive design. `css .sphere-canvas { width: 100%; height: 100%; position: absolute; top: 0; left: 0; z-index: 1; } nav { display: flex; color: white; z-index: 2; position: relative; padding: 4rem 8rem; justify-content: space-between; align-items: center; } nav a { text-decoration: none; color: white; font-weight: bold; } nav ul { display: flex; list-style: none; gap: 4rem; } h1 { color: white; z-index: 2; position: absolute; font-size: 3rem; left: 50%; top: 75%; transform: translate(-50%, -75%); } ` Head to the github repo to view the full code.** Check out the result: https://threlte6-spinning-ball.vercel.app/** Conclusion We've successfully added navigation and a scroll prompt to our Threlte project in the app.svelte` file. By layering 2D HTML content with a 3D scene, we've created an interactive user interface that combines traditional web design elements with immersive 3D visuals....

Avoiding Null and Undefined with NonNullable<T> in TypeScript cover image

Avoiding Null and Undefined with NonNullable<T> in TypeScript

Use Cases Use Case 1: Adding Two Numbers Scenario: A function that adds two numbers and returns their sum. But if one of the numbers is undefined or null, it returns the other number as-is. ` function addNumbers(a: number, b?: number | null): NonNullable { return a + (b ?? 0); } const sum1 = addNumbers(2, 3); // Returns 5 const sum2 = addNumbers(2, null); // Returns 2 const sum3 = addNumbers(2, undefined); // Returns 2 ` In this code snippet, the addNumbers()` function takes two parameters, `a` and `b`. `a` is a required parameter of type `number`, while `b` is an optional parameter of type `number` or `null`. The function uses the NonNullable&lt;T&gt; conditional type to ensure that the return value is not null or undefined. If `b` is null or undefined, the function returns the value of `a`. Otherwise, it adds `a` and `b` together and returns the sum. To handle the case where `b` is null or undefined, the code uses the nullish coalescing operator, `??`, which returns the value on its left-hand side if it is not null or undefined, and the value on its right-hand side otherwise. Use Case 2: Checking Contact Information Scenario: A class representing a person, but with optional email and phone properties. The contact()` function logs the email and phone numbers if they are defined and not null. Otherwise, it logs a message saying that no contact information is available. ` class Person { name: string; email?: string | null; phone?: string | null; constructor(name: string, email?: string | null, phone?: string | null) { this.name = name; this.email = email ?? null; this.phone = phone ?? null; } contact() { if(this.email !== undefined && this.email !== null && this.phone !== undefined && this.phone !== null) { console.log(${this.name} can be reached at ${this.email} or ${this.phone}`); } else { console.log(${this.name} has no contact information available`); } } } const john = new Person('John Doe', 'john.doe@example.com', '(123) 456-7890'); const jane = new Person('Jane Smith', null, '987-654-3210'); john.contact(); // logs 'John Doe can be reached at john.doe@example.com or (123) 456-7890' jane.contact(); // logs 'Jane Smith has no contact information available' ` Explanation: In this code snippet, the Person` class has a `name` property and optional `email` and `phone` properties. The `contact()` function checks if both `email` and `phone` are not undefined and not null before logging the details. Otherwise, it logs a message saying that no contact information is available. To initialize the properties with null, the constructor uses the nullish coalescing operator, `??`. When creating a new `Person`, you can pass null or undefined as arguments, and the class will interpret them as null. Conclusion Understanding and correctly implementing conditional types like NonNullable&lt;T&gt; in TypeScript is crucial to reduce potential code pitfalls. By reviewing examples of numerical operations and contact information handling, we've seen how this conditional type helps reinforce our code against null or undefined values. This highlights the utility of TypeScript's conditional types not only for enhancing code stability, but also for easing our coding journey. So keep experimenting and finding the best ways to deploy these tools for creating robust, secure, and efficient programs!...

Testing a Fastify app with the NodeJS test runner cover image

Testing a Fastify app with the NodeJS test runner

Introduction Node.js has shipped a built-in test runner for a couple of major versions. Since its release I haven’t heard much about it so I decided to try it out on a simple Fastify API server application that I was working on. It turns out, it’s pretty good! It’s also really nice to start testing a node application without dealing with the hassle of installing some additional dependencies and managing more configurations. Since it’s got my stamp of approval, why not write a post about it? In this post, we will hit the highlights of the testing API and write some basic but real-life tests for an API server. This server will be built with Fastify, a plugin-centric API framework. They have some good documentation on testing that should make this pretty easy. We’ll also add a SQL driver for the plugin we will test. Setup Let's set up our simple API server by creating a new project, adding our dependencies, and creating some files. Ensure you’re running node v20 or greater (Test runner is a stable API as of the 20 major releases) Overview `index.js` - node entry that initializes our Fastify app and listens for incoming http requests on port 3001 `app.js` - this file exports a function that creates and returns our Fastify application instance `sql-plugin.js` - a Fastify plugin that sets up and connects to a SQL driver and makes it available on our app instance Application Code A simple first test For our first test we will just test our servers index route. If you recall from the app.js` code above, our index route returns a 501 response for “not implemented”. In this test, we're using the createApp` function to create a new instance of our Fastify app, and then using the `inject` method from the Fastify API to make a request to the `/` route. We import our test utilities directly from the node. Notice we can pass async functions to our test to use async/await. Node’s assert API has been around for a long time, this is what we are using to make our test assertions. To run this test, we can use the following command: By default the Node.js test runner uses the TAP reporter. You can configure it using other reporters or even create your own custom reporters for it to use. Testing our SQL plugin Next, let's take a look at how to test our Fastify Postgres plugin. This one is a bit more involved and gives us an opportunity to use more of the test runner features. In this example, we are using a feature called Subtests. This simply means when nested tests inside of a top-level test. In our top-level test call, we get a test parameter t` that we call methods on in our nested test structure. In this example, we use `t.beforeEach` to create a new Fastify app instance for each test, and call the `test` method to register our nested tests. Along with `beforeEach` the other methods you might expect are also available: `afterEach`, `before`, `after`. Since we don’t want to connect to our Postgres database in our tests, we are using the available Mocking API to mock out the client. This was the API that I was most excited to see included in the Node Test Runner. After the basics, you almost always need to mock some functions, methods, or libraries in your tests. After trying this feature, it works easily and as expected, I was confident that I could get pretty far testing with the new Node.js core API’s. Since my plugin only uses the end method of the Postgres driver, it’s the only method I provide a mock function for. Our second test confirms that it gets called when our Fastify server is shutting down. Additional features A lot of other features that are common in other popular testing frameworks are also available. Test styles and methods Along with our basic test` based tests we used for our Fastify plugins - `test` also includes `skip`, `todo`, and `only` methods. They are for what you would expect based on the names, skipping or only running certain tests, and work-in-progress tests. If you prefer, you also have the option of using the describe` → `it` test syntax. They both come with the same methods as `test` and I think it really comes down to a matter of personal preference. Test coverage This might be the deal breaker for some since this feature is still experimental. As popular as test coverage reporting is, I expect this API to be finalized and become stable in an upcoming version. Since this isn’t something that’s being shipped for the end user though, I say go for it. What’s the worst that could happen really? Other CLI flags —watch` - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--watch —test-name-pattern` - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--test-name-pattern TypeScript support You can use a loader like you would for a regular node application to execute TypeScript files. Some popular examples are tsx` and `ts-node`. In practice, I found that this currently doesn’t work well since the test runner only looks for JS file types. After digging in I found that they added support to locate your test files via a glob string but it won’t be available until the next major version release. Conclusion The built-in test runner is a lot more comprehensive than I expected it to be. I was able to easily write some real-world tests for my application. If you don’t mind some of the features like coverage reporting being experimental, you can get pretty far without installing any additional dependencies. The biggest deal breaker on many projects at this point, in my opinion, is the lack of straightforward TypeScript support. This is the test command that I ended up with in my application: I’ll be honest, I stole this from a GitHub issue thread and I don’t know exactly how it works (but it does). If TypeScript is a requirement, maybe stick with Jest or Vitest for now 🙂...