Video

Watch the full video on YouTube:

Partial, Required and NonNullable in TypeScript

Introduction

In this ninth chapter of our TypeScript series, we’ll explore three fundamental utility types: Partial, Required, and NonNullable. These types allow us to transform existing types safely, making them more flexible or more strict according to our needs. They’re especially useful when working with data modeling, configuration creation, or encapsulating different types of configurations.

Since we’ve covered function typing, assertions, and basic generics usage, we can finally introduce these three basic utility types.

Partial

Partial is a type that lets us take all attributes of a type and make them optional. When I say optional, I mean each attribute will be either its natural type or undefined.

Basic Example

Let’s create a pet type with ID, name, owner, and years:

// models/pet.ts - Base pet type
type Pet = {
  id: number
  name: string
  owner: string
  years: number
}

Now suppose we have a function to update this pet, but we only want to update partial data:

// partial.ts - Using Partial for partial updates
import { Pet } from './models/pet.js'

// Partial converts all properties to optional
const updatePet = (pet: Partial<Pet>): void => {
  // We can update only the properties we need
}

// Only pass the name to update
updatePet({ name: "New name" })

If we inspect each attribute of our pet, we can see each has a question mark, preserving its type but also with undefined applied through a union.

Combining Partial with Omit

Normally our update functions don’t request the ID in the same data object. Let’s combine Partial with Omit:

// Combining Partial with Omit
const updatePet = (pet: Omit<Partial<Pet>, 'id'>): void => {
  // All data is optional except the ID
}

// The ID doesn't come in the object
updatePet({ name: "New name" }) // Valid

We can also do the inverse, first omit the ID then apply partial:

// Inverse order: first Omit, then Partial
const updatePet = (Partial<Omit<Pet, 'id'>>): void => {
  // Same result: all optional without ID
}

Use Case: Configurations

Partial is especially useful for configuration functions where the user only wants to configure some elements:

// Configuration with default values
type Config = {
  host: string
  port: number
  dns: string
}

// Function receives partial configuration
const configure = (config: Partial<Config>): void => {
  // Internally we have default values
}

// Only configure the host
configure({ host: "localhost" })

Required

Required does the complete opposite of Partial. Required lets you take an object with optional values and make absolutely all attributes required.

Basic Example

Let’s create a type with optional properties and transform it:

// models/pet.ts - Pet with optional owner
type Pet = {
  id: number
  name: string
  owner?: string // Optional
  years: number
}

// Required converts all properties to required
type MandatoryAttributes = Required<Pet>
// Now owner is required, without undefined

By doing this, every attribute that Pet has is now required. Owner no longer has undefined.

Use Case: Configuration with Default Values

Required is especially useful when developing an SDK or library:

// SDK Configuration
type Config = {
  host?: string
  port?: number
  dns?: string
}

// Internal default values
const defaultConfig: Required<Config> = {
  host: "localhost",
  port: 5432,
  dns: "my-dns"
}

// Function that resolves external config with internal
const setupSDK = (options: Partial<Config>): Required<Config> => {
  return { ...defaultConfig, ...options }
}

// User can overwrite values
const configured = setupSDK({ host: "192.168.1.1" })
// configured has all required values

This is a very common practical example when creating libraries. We create a configuration stored internally with default values, and give the user the ability to overwrite those values with what they need.

NonNullable

NonNullable interacts with types that are union types. Its responsibility is to remove null and undefined types from our unions.

Basic Example

// Type that can be null or undefined
type EmailString = string | null | undefined

// NonNullable removes null and undefined
type NonNullableEmailString = NonNullable<EmailString>
// Only remains: string

Example with Literals

Let’s see a more extensive example with union literals:

// Animal species type
type AnimalSpecies = 
  | "mammals"
  | "amphibians"
  | "reptiles"
  | "birds"
  | "fishes"
  | null // Unrecognized species

// NonNullable removes null
type ValidAnimalSpecies = NonNullable<AnimalSpecies>
// Only remains: "mammals" | "amphibians" | "reptiles" | "birds" | "fishes"

If we add undefined, NonNullable also removes it, regardless of where we put it.

Creating Derived Types with Indexing

NonNullable is especially useful when working with creating new objects from others and accessing attributes in an indexed way:

// Pet with optional owner
type Pet = {
  id: number
  name: string
  owner?: string
  years: number
}

// Create derived type with required owner and no undefined
type MyPetType = Omit<Pet, 'owner'> & {
  owner: NonNullable<Pet['owner']>
}
// owner is string (without undefined)

We access the type directly from the object using bracket syntax, as if it were a JSON and we try to access by key.

Best Practices

  1. Use Partial whenever you want to make all attributes of an object optional
  2. Use Required when you have an object with optional attributes and want all to be required
  3. Use NonNullable whenever you want to remove null or undefined from your types
  4. Combine Partial with Omit to create safer update functions
  5. Use Required for configurations with default values in SDKs and libraries
  6. Access property types with Type['propertyName'] syntax to create derived types

Conclusion

Partial, Required, and NonNullable are fundamental tools for transforming types in TypeScript. Partial lets us create flexible versions of our types, Required guarantees completeness, and NonNullable eliminates the ambiguity of null values. Together, these utility types give us precise control over the shape and strictness of our types, improving the safety and clarity of our code.

Repository

💻 Repository: https://github.com/radagva/ts-example

NestJS API Development Guide 08: PUT, PATCH, DELETE Methods and DTOs with PartialType