Docs Menu
Docs Home
/ / /
Node.js Driver
/

TypeScript

On this page

  • Overview
  • Features
  • Type Parameters that Extend Document
  • Type Parameters of Any Type
  • Type Safety and Dot Notation
  • Working with the _id Field
  • Insert Operations and the _id Field
  • Find Methods and the _id Field
  • Known Limitations
  • Recursive Types and Dot Notation
  • Mutual Recursion

In this guide, you can learn about the TypeScript features and limitations of the MongoDB Node.js driver. TypeScript is a strongly typed programming language that compiles to JavaScript.

The TypeScript compiler offers type checking in real time. Code editors that support TypeScript can provide autocomplete suggestions, display documentation inline, and identify type-related errors.

All TypeScript features of the driver are optional. All valid JavaScript code written with the driver is also valid TypeScript code.

For more information, see the TypeScript website.

If you use TypeScript, you can specify a type for some classes in the driver. All classes that accept a type parameter in the driver have the default type Document. The Document interface has the following definition:

interface Document {
[key: string]: any;
}

All object types extend the Document interface.

For more information on object types, see the TypeScript handbook.

The following classes accept all types that extend the Document interface:

  • Collection

  • ChangeStream

You can pass a type parameter that extends the Document interface like this:

1interface Pet {
2 name: string;
3 age: number;
4}
5
6const database = client.db("<your database>");
7const collection = database.collection<Pet>("<your collection>");

Important

Keys Not in Type Parameter Receive any Type

Keys not listed in your specified type parameter receive the any type. The following code snippet demonstrates this behavior:

1interface User {
2 email: string;
3}
4
5const database = client.db("<your database>");
6const myColl = db.collection<User>("<your collection>");
7myColl.find({ age: "Accepts any type!" });

The following classes accept all type parameters:

You can find a code snippet that shows how to specify a type for the FindCursor class in the Find Multiple Documents Usage Example.

Starting in version 5.0, by default, the Node.js driver does not provide type safety for operations that search on fields expressed in dot notation. Dot notation is a syntax you can use to navigate nested JSON objects. When you construct a filter to pass to a query, the driver will not raise a type error even if you specify an incorrectly typed value for a field expressed in dot notation.

The following code snippet defines the ClassificationPet interface, which includes a classification field that enables you to specify the genus and color of dogs and cats:

interface ClassificationPet {
name: string;
age: number;
classification: { genus: "Canis" | "Felis"; color: string };
}

The driver does not raise a type error for the following code sample, even though the value of classification.color is a boolean instead of a string:

await myColl.findOneAndDelete({ "classification.color": false });

You can enable type-checking by constructing filters as StrictFilter or StrictUpdateFilter types.

Warning

The StrictFilter and StrictUpdateFilter types are experimental and might incorrectly show type errors in valid queries.

In the following code sample, the filter is assigned a StrictFilter type. Given this filter type, the Node.js driver reports a type error because the value of classification.color is a boolean instead of a string.

const filterPredicate: StrictFilter<ClassificationPet> = { "classification.color": false };
await myColl.findOneAndDelete(filterPredicate);

The following example assigns a StrictUpdateFilter type to an update filter. The Node.js driver reports a type error because the value of classification.color is a boolean instead of a string.

const updateFilter: StrictUpdateFilter<ClassificationPet> = { $set: { "classification.color": false } }
await pets.updateOne({}, updateFilter);

To query a collection or perform another operation with a key that incorporates variables, you must use an as const assertion when specifying the key. This mechanism allows your code to compile successfully if the input types are correct.

The following code snippet defines the ClassificationPet interface and the Mealtime interface. ClassificationPet includes a mealtimes field that contains an array of Mealtime interfaces, each of which includes a time field:

interface ClassificationPet {
name: string;
mealtimes: Mealtime[];
}
interface Mealtime{
time: string;
amount: number;
}

The following code snippet performs a find-and-update operation on a collection of ClassificationPet documents. The operation updates the nested time field of the Mealtime instance at index 1. The index position is specified by the variable mealCounter:

const mealCounter = 1;
await myColl.findOneAndUpdate(
{ name: "Lassie" },
{ $set: { [`mealtimes.${mealCounter}.time` as const]: '04:00 PM' } },
);

To learn more about dot notation, see Dot Notation in the MongoDB manual.

To learn more about the limitations of dot notation in the Node.js driver, see the Recursive Types and Dot Notation section.

MongoDB does not recommend specifying the _id as a part of your model. Omitting the _id field makes the model more generic and reusable and more accurately models the data important to an application. The Node driver’s TypeScript integration takes care of adding the _id field to the return types for relevant methods.

The following sections provide information about write and read operations that use the _id field.

How you specify the _id field in type parameters passed to your Collection instance affects the behavior of insert operations. The following table describes how different _id field specifications affect insert operations:

_id field type
Example Type
Required on insert
Behavior on insert
Unspecified
Not applicable
No
The driver creates an ObjectId value for each inserted document.
Specified
{ _id: number };
Yes
If you do not specify a value for the _id field in an insert operation, the driver raises an error.
Specified as optional
{ _id?: number };
No
If you do not specify the _id field in an insert operation, the driver adds an _id field value generated by the primary key factory.

If you must specify the _id field as required in the type you define to represent documents in your collection but you do not want to specify values for the _id field in insert operations, use the OptionalId helper type when you create your collection. The OptionalId type accepts a type parameter as an argument and returns that type with an optional _id field.

The following code snippet defines the IdPet interface, which includes a type for the _id field:

interface IdPet {
_id: ObjectId;
name: string;
age: number;
}

The following code uses the preceding interface and the OptionalId type to insert a document without specifying a value for the _id field:

const database = client.db("<your database>");
const collection = db.collection<OptionalId<IdPet>>("<your collection>");
myColl.insertOne({
name: "Spot",
age: 2
});

To learn more about the _id field, see The _id Field in the MongoDB manual.

To learn more about the types, interfaces, and classes discussed in this section, see the following resources:

The find and findOne methods of the Collection class include the _id field in their return type. The driver infers the type of the returned _id field based on the type parameter you passed to your Collection instance.

If the type parameter you passed to your Collection instance includes the _id field in its schema, the driver infers that the _id field returned from the method is of the type specified in the schema.

However, if the type parameter you passed to your Collection instance does not include the _id field in its schema, the driver infers that the type of the _id field returned from the method is ObjectId.

Tip

The type parameter passed to your Collection influences only the type inference of the fields returned from the method. The driver does not convert the field to the specified type. The type of each field in your type parameter's schema must match the type of the corresponding field in the collection.

The following code uses the Pet interface to return a document with an _id inferred to be of type ObjectId:

const database = client.db("<your database>");
const collection = db.collection<Pet>("<your collection>");
const document = await myColl.findOne({
name: "Spot",
});
const id : ObjectId = document._id;

The following code uses the IdNumberPet interface to return a document with an _id inferred to be of type number:

interface IdNumberPet {
_id: number;
name: string;
age: number;
}
const database = client.db("<your database>");
const collection = db.collection<IdNumberPet>("<your collection>");
const document = await myColl.findOne({
name: "Spot",
});
const id : number = document._id;

Important

Projection

If you specify a projection in a find method, you must pass a type parameter to your find method that reflects the structure of your projected documents. Without a type parameter, TypeScript cannot check at compile time that you are using your projected documents safely.

To show this behavior, the following code snippet passes type checking but raises an error at runtime:

const doc = await myColl.findOne(
{},
{ projection: { _id: 0, name: 1 } }
);
console.log(doc._id.generationTime);

To catch this error at compile time, pass a type parameter that does not include the _id field to your find method:

interface ProjectedDocument {
name: string
}
const doc = await myColl.findOne<ProjectedDocument>(
{},
{ projection: { _id: 0, name: 1 } }
);
// Compile time error: Property '_id' does not exist on type 'ProjectedDocument'.
console.log(doc._id.generationTime);

To view a runnable TypeScript example that includes a find method applying a projection, see the Find a Document page.

To learn more about the classes and methods discussed in this section, see the following API documentation:

Learn about the following TypeScript specific limitations of the Node.js driver:

The Node.js driver cannot provide type safety within nested instances of recursive types referenced through dot notation.

A recursive type is a type that references itself. You can update the Pet interface to be recursive by allowing a pet to have its own pet. The following is the recursive Pet interface:

interface RecursivePet {
pet?: RecursivePet;
name: string;
age: number;
}

Note

Depth Limit

The Node.js driver does not traverse nested recursive types when type checking dot notation keys to avoid hitting TypeScript's recursive depth limit.

The following code snippet references a nested instance of the RecursivePet interface with an incorrect type using dot notation, but the TypeScript compiler does not raise a type error:

database
.collection<RecursivePet>("<your collection>")
.findOne({ "pet.age": "Spot" });

The following code snippet references a top-level instance of the RecursivePet interface with an incorrect type and raises a type error:

database
.collection<RecursivePet>("<your collection>")
.findOne({ pet: "Spot" });

The error raised by the preceding code snippet is as follows:

index.ts(19,59): error TS2769: No overload matches this call.
The last overload gave the following error.
Type 'string' is not assignable to type 'Condition<Pet>'.

If you must have type safety within nested instances of recursive types, you must write your query or update without dot notation.

To learn more about dot notation, see Dot Notation in the MongoDB manual.

A mutually recursive type exists when two types contain a property that is of the other's type. You can update the Pet interface to be mutually recursive by allowing a pet to have a handler, and defining a handler to have a pet. The following examples reference the mutually recursive Pet and Handler interfaces:

interface Pet {
handler?: Handler;
name: string;
age: number;
}
interface Handler {
pet: Pet;
name: string;
}

The Node.js driver provides type safety for mutually recursive types referenced through dot notation up to a depth of eight. The following code snippet assigns a string to a number and raises a type error because the referenced property is at a depth of four:

database
.collection<Pet>("<your collection>")
.findOne({'handler.pet.handler.pet.age': "four"});

The error raised by the preceding code snippet is as follows:

index.ts(19,59): error TS2769: No overload matches this call.
The last overload gave the following error.
Type 'string' is not assignable to type 'Condition<number> | undefined'.

At a depth greater than or equal to eight, TypeScript compiles your code but no longer type checks it. The following code assigns a string to a number property but does not cause a compilation error because the referenced property is at a depth of 10:

database
.collection<Pet>("<your collection>")
.findOne({'handler.pet.handler.pet.handler.pet.handler.pet.handler.pet.age': "four"});

Back

Time Series