Strongly typed services with Mongoose and TypeScript

In a previous post, we explored how to integrate TypeScript with your Mongoose models. In this post, we’ll move up to the service layer and see how we can use Mongoose types to get even more out of your application's Mongoose & Typescript implementations.

Image for post
Image for post

Firstly let’s answer some basic questions about the service layer: what is a service layer and how do I use it?

The service layer is used to encapsulate implementations of an application’s business logic and provide an API for invocation of that logic in a consistent way.

Services are functions that perform common tasks on your data, such as reading, writing, updating, and deleting. Services are often called by controllers or resolvers, and a single service is often used by several different functions.

For a practical guide on the service layer and how it can be used in your JavaScript applications, read the NestJS documentation on providers.

This guide will include several different examples of code that lives inside a service layer. It will also include examples of code that uses the services. If you have used NestJS, the syntax used will look familiar. However, this guide is not about resolvers or services, it is about how you can use Mongoose’s types at the service layer.

To get started using Mongoose types, you will need to install the Mongoose package from DefinitelyTyped:

Query methods

It’s common to have a service that is responsible for fetching data by a certain field, much like Mongoose’s method.

For example, an application that needs to find users by email addresses will benefit from having a service-level method for this task.

In this trivial example, we can simply type our input as the email address from the user model. This allows us to communicate with other developers where the data comes from and what its type is.

Note that if the email field is nullable on the user interface, the input will be nullable here too. This may be what you want, but it may also lead to some unexpected results.

What happens when the application requires a more generic, reusable method? We use Mongoose’s built-in types to tell the controller, or resolver what the method expects.

Image for post
Image for post
Syntax highlighting shows that the services will accept any property from the user model

You’ll notice that is a generic type that takes your type as a parameter. If you see an error here, make sure your interface extends . This is a common pattern that you will see throughout this guide.

Using options is just as easy with the `QueryFindBaseOptions` type.

In the above example, we included a default value for options, making the input optional.

Image for post
Image for post
Syntax highlighting shows what options the method will accept

Both of these usages are valid:

Update methods

Update methods follow the same pattern as above, but with at least two inputs, and . The options input can also be typed with the interface.

Another common practice is to combine the types from the object interface with the types from your model and the types from Mongoose can be combined to create services that meet your business logic requirements.

The above examples are trivial, but illustrate how you can use the Mongoose DefinitelyTyped package to make your services even easier to use and more reliable.

Further reading:

Available types: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/mongoose/index.d.ts#L3775

Strongly typed models with Mongoose and TypeScript: https://medium.com/@tomanagle/strongly-typed-models-with-mongoose-and-typescript-7bc2f7197722

Written by

I am a full stack JavaScript developer, living in Melbourne, Australia. My preferred stack is Mongoose, TypeScript, Node.js, React & GraphQL.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store