Skip to content

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<number> {
  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<T> 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<T> 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!

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

Introducing the New Shopify and Next.js 13 Starter Kit cover image

Introducing the New Shopify and Next.js 13 Starter Kit

Intro We are delighted to announce our brand new Shopify and Next.js 13 starter kit to our Starter.dev kits collection. This Starter kit is amazing to help you and your team quickly bootstrap a custom Shopify storefronts powered by Next.js 13 App Router since it has almost every feature you need for production set up and running! Why did we build it? At This Dot Labs, we work with a variety of clients, from big corporations to startups. Recently, we realized that we have a lot of clients who use Shopify that want to build custom stores using Next.js to benefit from its features. Additionally, we know that Hydrogen Shopify has only been limited to Remix since they acquired it. We had a lot of a hard time refilling the gap Hydrogen left to implement its features from scratch in our client projects. And from here, we realized that we need to build a starter kit to: - Help our teams build custom storefronts using Next.js Quickly - Reduce the cost for our clients There were a lot of Next.js Shopify starters out there, but they were very basic and didn’t have all the features our teams needed, unlike the Official Hydrogen Starter Example which is complete. So, we decided to convert the Hydrogen starter from Remix/Hydrogen to Next.js, and use the power of the App Router and React Server Components. Tech Stack We used in this project: - Next.js 13 - TailwindCSS - Zustand Why Shopify Shopify is a great e-commerce platform. They have a lot of experience in this field since 2006, and now over 4.12 million e-commerce sites are built with Shopify. (Source: Builtwith.com.) Shopify is used by online sellers in over 175 countries around the world, with 63% of Shopify stores estimated to be based in the US. Why Next.js 13 As we mentioned above, we have a lot of clients who already use Next.js and it’s a very popular framework in the market for its flexibility, scalability, and diverse collection of features. Features Since this starter kit is based on the official Shopify Hydrogen template, it has all of it’s features as well including: - Light/Dark themes support - Authentication system with login, register, forgot password, and account pages - Supports both Mobile/Desktop screens - Has built-in infinity scroll pagination, built with Server Components - State management using Zustand - Variety of custom fonts - All components have been built with Tailwind - Static analysis testing tools pre-configured like TypeScript, Eslint, Prettier, and a lot of useful extensions - Storybooks for the consistency of the system design - GitHub Pull requests and release templates - Shopify analytics integrated with the kit - Great performance and SEO scores - And finally, incompatible performance tested by both Lighthouse and PerfBuddy Performance This kit is running at top performance! It has amazing performance, SEO, and accessibility scores. We tested it using PerfBuddy, and the results are incredible on both Desktop and Mobile. Here is the full report. > Note: PerfBuddy is an incredible free tool to analyze the performance of your web apps accurately. Check it out! Also, the results from Lighthouse are pretty fascinating: When do I use this kit? This starter kit is great for starting new custom storefronts rapidly, especially for startups to save time and money. Also, it can be used to migrate from the old Next.js storefront to the new App router directory as it has all of the examples your team will need to integrate Shopify in Next.js 13 Conclusion We are very excited to share this starter kit with you, and we hope you find it useful for your projects. We are looking forward to hearing your feedback and suggestions to improve it, and add more features to it. Also, we are planning to add more starter kits to our Starter.dev collection, so stay tuned for more updates!...

How to host a full-stack app with AWS CloudFront and Elastic Beanstalk cover image

How to host a full-stack app with AWS CloudFront and Elastic Beanstalk

How to host a full-stack JavaScript app with AWS CloudFront and Elastic Beanstalk Let's imagine that you have finished building your app. You have a Single Page Application (SPA) with a NestJS back-end. You are ready to launch, but what if your app is a hit, and you need to be prepared to serve thousands of users? You might need to scale your API horizontally, which means that to serve traffic, you need to have more instances running behind a load balancer. Serving your front-end using a CDN will also be helpful. In this article, I am going to give you steps on how to set up a scalable distribution in AWS, using S3, CloudFront and Elastic Beanstalk. The NestJS API and the simple front-end are both inside an NX monorepo The sample application For the sake of this tutorial, we have put together a very simple HTML page that tries to reach an API endpoint and a very basic API written in NestJS. The UI The UI code is very simple. There is a "HELLO" button on the UI which when clicked, tries to reach out to the /api/hello` endpoint. If there is a response with status code 2xx, it puts an `h1` tag with the response contents into the div with the id `result`. If it errors out, it puts an error message into the same div. `html Frontend HELLO const helloButton = document.getElementById('hello'); const resultDiv = document.getElementById('result'); helloButton.addEventListener('click', async () => { const request = await fetch('/api/hello'); if (request.ok) { const response = await request.text(); console.log(json); resultDiv.innerHTML = ${response}`; } else { resultDiv.innerHTML = An error occurred.`; } }); ` The API We bootstrap the NestJS app to have the api` prefix before every endpoint call. `typescript // main.ts import { Logger } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { AppModule } from './app/app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); const globalPrefix = 'api'; app.setGlobalPrefix(globalPrefix); const port = process.env.PORT || 3000; await app.listen(port); Logger.log(🚀 Application is running on: http://localhost:${port}/${globalPrefix}`); } bootstrap(); ` We bootstrap it with the AppModule which only has the AppController in it. `typescript // app.module.ts import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; @Module({ imports: [], controllers: [AppController], }) export class AppModule {} ` And the AppController sets up two very basic endpoints. We set up a health check on the /api` route and our hello endpoint on the `/api/hello` route. `typescript import { Controller, Get } from '@nestjs/common'; @Controller() export class AppController { @Get() health() { return 'OK'; } @Get('hello') hello() { return 'Hello'; } } ` Hosting the front-end with S3 and CloudFront To serve the front-end through a CMS, we should first create an S3 bucket. Go to S3 in your AWS account and create a new bucket. Name your new bucket to something meaningful. For example, if this is going to be your production deployment I recommend having -prod` in the name so you will be able to see at a glance, that this bucket contains your production front-end and nothing should get deleted accidentally. We go with the defaults for this bucket setting it to the us-east-1` region. Let's set up the bucket to block all public access, because we are going to allow get requests through CloudFront to these files. We don't need bucket versioning enabled, because these files will be deleted every time a new front-end version will be uploaded to this bucket. If we were to enable bucket versioning, old front-end files would be marked as deleted and kept, increasing the storage costs in the long run. Let's use server-side encryption with Amazon S3-managed keys and create the bucket. When the bucket is created, upload the front-end files to the bucket and let's go to the CloudFront service and create a distribution. As the origin domain, choose your S3 bucket. Feel free to change the name for the origin. For Origin access, choose the Origin access control settings (recommended)`. Create a new Control setting with the defaults. I recommend adding a description to describe this control setting. At the Web Application Firewall (WAF) settings we would recommend enabling security protections, although it has cost implications. For this tutorial, we chose not to enable WAF for this CloudFront distribution. In the Settings section, please choose the Price class that best fits you. If you have a domain and an SSL certificate you can set those up for this distribution, but you can do that later as well. As the Default root object, please provide index.html` and create the distribution. When you have created the distribution, you should see a warning at the top of the page. Copy the policy and go to your S3 bucket's Permissions` tab. Edit the `Bucket policy` and paste the policy you just copied, then save it. If you have set up a domain with your CloudFront distribution, you can open that domain and you should be able to see our front-end deployed. If you didn't set up a domain the Details section of your CloudFront distribution contains your distribution domain name. If you click on the "Hello" button on your deployed front-end, it should not be able to reach the /api/hello` endpoint and should display an error message on the page. Hosting the API in Elastic Beanstalk Elastic beanstalk prerequisites For our NestJS API to run in Elastic Beanstalk, we need some additional setup. Inside the apps/api/src` folder, let's create a `Procfile` with the contents: `web: node main.js`. Then open the `apps/api/project.json` and under the `build` configuration, extend the `production` build setup with the following (I only ) `json { "targets": { "build": { "configurations": { "development": {}, "production": { "generatePackageJson": true, "assets": [ "apps/api/src/assets", "apps/api/src/Procfile" ] } } } } } ` The above settings will make sure that when we build the API with a production configuration, it will generate a package.json` and a `package-lock.json` near the output file `main.js`. To have a production-ready API, we set up a script in the package.json` file of the repository. Running this will create a `dist/apps/api` and a `dist/apps/frontend` folder with the necessary files. `json { "scripts": { "build:prod": "nx run-many --target=build --projects api,frontend --configuration=production" } } ` After running the script, zip the production-ready api folder so we can upload it to Elastic Beanstalk later. `bash zip -r -j dist/apps/api.zip dist/apps/api ` Creating the Elastic Beanstalk Environment Let's open the Elastic Beanstalk service in the AWS console. And create an application. An application is a logical grouping of several environments. We usually put our development, staging and production environments under the same application name. The first time you are going to create an application you will need to create an environment as well. We are creating a Web server environment`. Provide your application's name in the `Application information` section. You could also provide some unique tags for your convenience. In the `Environment information` section please provide information on your environment. Leave the `Domain` field blank for an autogenerated value. When setting up the platform, we are going to use the Managed Node.js platform with version 18 and with the latest platform version. Let's upload our application code, and name the version to indicate that it was built locally. This version label will be displayed on the running environment and when we set up automatic deployments we can validate if the build was successful. As a Preset, let's choose Single instance (free tier eligible)` On the next screen configure your service access. For this tutorial, we only create a new service-role. You must select the aws-elasticbeanstalk-ec2-role` for the EC2 instance profile. If can't select this role, you should create it in AWS IAM with the AWSElasticBeanstalkWebTier`, `AWSElasticBeanstalkMulticontainerDocker` and the `AWSElasticBeanstalkRoleWorkerTier` managed permissions. The next step is to set up the VPC. For this tutorial, I chose the default VPC that is already present with my AWS account, but you can create your own VPC and customise it. In the Instance settings` section, we want our API to have a public IP address, so it can be reached from the internet, and we can route to it from CloudFront. Select all the instance subnets and availability zones you want to have for your APIs. For now, we are not going to set up a database. We can set it up later in AWS RDS but in this tutorial, we would like to focus on setting up the distribution. Let's move forward Let's configure the instance traffic and scaling. This is where we are going to set up the load balancer. In this tutorial, we are keeping to the defaults, therefore, we add the EC2 instances to the default security group. In the Capacity` section we set the `Environment type` to `Load balanced`. This will bring up a load balancer for this environment. Let's set it up so that if the traffic is large, AWS can spin up two other instances for us. Please select your preferred tier in the `instance types` section, We only set this to `t3.micro` For this tutorial, but you might need to use larger tiers. Configure the Scaling triggers` to your needs, we are going to leave them as defaults. Set the load balancer's visibility to the public and use the same subnets that you have used before. At the Load Balancer Type` section, choose `Application load balancer` and select `Dedicated` for exactly this environment. Let's set up the listeners, to support HTTPS. Add a new listener for the 443 port and connect your SSL certificate that you have set up in CloudFront as well. For the SSL policy choose something that is over TLS 1.2 and connect this port to the default` process. Now let's update the default process and set up the health check endpoint. We set up our API to have the health check endpoint at the /api` route. Let's modify the default process accordingly and set its port to 8080. For this tutorial, we decided not to enable log file access, but if you need it, please set it up with a separate S3 bucket. At the last step of configuring your Elastic Beanstalk environment, please set up Monitoring, CloudWatch logs and Managed platform updates to your needs. For the sake of this tutorial, we have turned most of these options off. Set up e-mail notifications to your dedicated alert e-mail and select how you would like to do your application deployments`. At the end, let's configure the Environment properties`. We have set the default process to occupy port 8080, therefore, we need to set up the `PORT` environment variable to `8080`. Review your configuration and then create your environment. It might take a few minutes to set everything up. After the environment's health transitions to OK` you can go to AWS EC2 / Load balancers in your web console. If you select the freshly created load balancer, you can copy the DNS name and test if it works by appending `/api/hello` at the end of it. Connect CloudFront to the API endpoint Let's go back to our CloudFront distribution and select the Origins` tab, then create a new origin. Copy your load balancer's URL into the `Origin domain` field and select `HTTPS only` protocol if you have set up your SSL certificate previously. If you don't have an SSL certificate set up, you might use `HTTP only`, but please know that it is not secure and it is especially not recommended in production. We also renamed this origin to `API`. Leave everything else as default and create a new origin. Under the Behaviors` tab, create a new behavior. Set up the path pattern as `/api/*` and select your newly created `API` origin. At the `Viewer protocol policy` select `Redirect HTTP to HTTPS` and allow all HTTP methods (GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE). For this tutorial, we have left everything else as default, but please select the Cache Policy and Origin request policy that suits you the best. Now if you visit your deployment, when you click on the HELLO` button, it should no longer attach an error message to the DOM. --- Now we have a distribution that serves the front-end static files through CloudFront, leveraging caching and CDN, and we have our API behind a load balancer that can scale. But how do we deploy our front-end and back-end automatically when a release is merged to our main` branch? For that we are going to leverage AWS CodeBuild and CodePipeline, but in the next blog post. Stay tuned....

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....

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 🙂...