Mongoose, Typescript and Async/Await
Maybe you’ve arrived here while curiously poking around at Mongoose with async/await, or perhaps Typescript is just choking on your queries.
In any case, I’ll walk through writing schema, a model, creating a document, and returning a document all-the-while keeping Typescript happy.
In this article, I’ll demonstrate:
- Creating types for models
- Using Mongoose and Typescript with async/await
- How to use Typescipt with Mongoose’s lean method
Typescript with Mongoose Models
First, let’s create our types. In this case I’ll create a user interface and generate a corresponding Mongoose document type:
import mongoose, { Document } from 'mongoose'interface ITodo {
title: string
message: string
}interface ITodoDoc extends Document, ITodo {}
ITodoDoc
extends Mongoose’s Document type — which is the standard return value for a query. It also extends ITodo
. This combines our type with the Mongoose Document type, which allows us to tell Typescipt what to expect so it can infer this output in the future.
Next, we’ll go ahead and create out schema and model, passing in ITodoDoc
as a type argument to model:
const todoSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true
},
message: {
type: String,
required: true
}
})const TodoModel = mongoose.model<ITodoDoc>('todo', todoSchema)
Adding the ITodoDoc
argument to the model is what allows the inference to occur.
Typescript, async/await, and Mongoose Create
Let’s create our first Todo:
interface createTodoArgs {
title: string
message: string
}const createTodo = async ({title, message}: createTodoArgs): ITodoDoc => {
let todo: TodoDoc try {
todo = await TodoModel.create({ title, message })
} catch(ex) {
throw new Error(ex)
} return todo
}
Since we’re using create
and async/await, our data is returned in a very straight forward manner without any callback boilerplate.
Typescript, async/await, and Mongoose Queries
Now let’s write a basic query:
const getTodo = async (id: string): ITodoDoc => {
let todo: TodoDoc try {
todo = await TodoModel.findById(id)
} catch(ex) {
throw new Error(ex)
} return todo
}
Again, pretty simple. Let’s take a look at a variation without try/catch to examine what happens with and without a typed model:
...const TodoModel = mongoose.model('todo', todoSchema)const getTodo = async (id: string): ITodoDoc => { // todo is equal to ITodoDoc
const todo = await TodoModel.findById(id) // typescript won't recognize title as a property of todo
if(todo.title) {
...
}
return todo
}
Without a typed model, Typescript will choke at the if statement, as it would not know todo
has a title
property, and would only identify the basic Mongoose Document type properties. However, with the following line from a previous example above…
const TodoModel = mongoose.model<ITodoDoc>('todo', todoSchema)
…it would recognize that todo
has the property title
, and eliminate the error feedback.
Let’s look at a trickier example: lean
Typescript and Mongoose’s Lean Method
What is lean
? From Mongoose:
The lean option tells Mongoose to skip hydrating the result documents. This makes queries faster and less memory intensive, but the result documents are plain old JavaScript objects (POJOs), not Mongoose documents.
This means that, instead of your todo query returning ITodoDoc
, it would simply return ITodo
, so you’ll no longer have to destructure the properties you do need from the returned Document. The gotcha here is that, when using lean
, you must explicitly tell Typescript that the return type, ITodo
, will differ from the type assigned to our TodoModel,ITodoDoc
. If we don’t, Typescript will choke, and it might leave you scratching your head.
Here’s an example of properly using Typescript with lean:
const getTodo = async (id: string): ITodo => { // todo is equal to ITodo
const todo = await TodoModel.findById(id).lean<ITodo>()
if(todo.title) {
...
}
return todo
}
This works because lean
accepts a generic, and passes it into the Query
type:
lean<T>(bool?: boolean | object): Query<T> & QueryHelpers;
That’s all, folks!
Although this is a very rudimentary glimpse, I hope it helps you along your development journey.