TypeScript
On this page
Overview
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.
Features
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.
Type Parameters that Extend Document
The following classes accept all types that extend the Document
interface:
You can pass a type parameter that extends the Document
interface like this:
1 interface Pet { 2 name: string; 3 age: number; 4 } 5 6 const database = client.db("<your database>"); 7 const 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:
1 interface User { 2 email: string; 3 } 4 5 const database = client.db("<your database>"); 6 const myColl = db.collection<User>("<your collection>"); 7 myColl.find({ age: "Accepts any type!" });
Type Parameters of 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.
Type Safety and Dot Notation
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);
Referencing Keys that Incorporate Variables
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.
Working with the _id Field
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.
Insert Operations and 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:
OptionalId API documentation
PkFactory API documentation
ObjectId source code
Find Methods and the _id Field
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:
Known Limitations
Learn about the following TypeScript specific limitations of the Node.js driver:
No type safety for dot notation references to nested instances of recursive types
Depth limitations on type safety for mutually recursive types
Recursive Types and Dot Notation
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.
Mutual Recursion
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"});