Announcing "@mswjs/data"—data modeling library for testing JavaScript applications
Published: 2021-04-12 15:59:45

## Introduction

It's been more than a year since [Mock Service Worker](https://mswjs.io) (MSW) began to appear in people's `package.json`, improving the way you write tests, iterate on features, and debug API-related issues. We are incredibly thankful for everybody who supported us and gave the library a chance in their projects. That allowed us to gather a ton of feedback based on the usage scenarios you face every day. It is with that feedback that we can move the project forward to ease your testing and development workflow even more. And it is with that feedback that we are able to make this announcement.

MSW was deliberately designed with only the essentials of API mocking in mind: interception of requests and response mocking. A huge focus was made on leveraging Service Worker API to enable a one-of-a-kind experience and support the same request handlers across different environments. While some alternative libraries come with built-in assertions or data-modeling options, our team has chosen a horizontal way to scale the project: distribute complimentary, on-demand libraries instead of stuffing dozens of functions and methods into a single package.

Some of the most popular questions developers have when starting with MSW are related to data. You immediately notice how the library is agnostic to how you create and update data in your handlers. You may use a plain array or `Map` to manage resources. Perhaps, you may even design a custom database abstraction that manages those resources in a more standardized way.

No matter what data-related setup you end up with, it's there to answer the following questions:

- How to describe data resources?
- How to implement CRUD operations on data?
- How to persist the changes made to the data?

Today we are proud to announce the [Data library](https://github.com/mswjs/data)—a standalone package to model and query data in your tests and beyond. Although the project is at the early stage of development and doesn't solve every problem right away, letting you try it out and gather your feedback is crucial for us to refine and shape the future experience.

{% github mswjs/data no-readme %}

Without further a due, let's talk about what's to become the recommended way to work with data in Mock Service Worker.

---

## Data modeling

First, let's get acquainted with the two main terms that the Data library operates with:

- _Model_—description of the data. Think of it as a blueprint that describes what properties the data has.
- _Entity_—an instance of a particular model. This is the exact data that implements its model description.

When working with Data, you define models and relationships between them, which, effectively, result in a virtual database being created.

Install the package into your project:

```bash
npm install @mswjs/data 
```

Now, let's create a new "user" model:

```js
import { factory, primaryKey } from '@mswjs/data'

const db = factory({
  user: {
    id: primaryKey(Math.random),
    firstName: () => 'John',
    age: () => 18,
  },
})
```

Models are defined by calling the `factory` function and providing it with the object where keys represent _model names_, and values stand for _model definitions_. Each property in the model definition has an initializer—a function that seeds a value and infers its type.

Notice how the `id` property equals `primaryKey`. Each model _must have a primary key_, which acts as a unique ID in a conventional database table. Data exposes the `primaryKey` function that you should use to mark a certain property as the model's primary key.

In the example above, we're using plain functions that return static data. This means that each time a user is created, it will have `firstName: "John"` and `age: 18`. While this is a good foundation to build upon, the static nature of values may limit your data scenarios. Consider using tools like [Faker](https://github.com/marak/Faker.js/) to define models with randomly generated realistic data.

```js
import { factory, primaryKey } from '@mswjs/data'
import { name, random } from 'faker'

const db = factory({
  // Create a user model with a random UUID and first name.
  user: {
    id: primaryKey(random.uuid),
    firstName: name.findName,
    age: random.number,
  },
})
```

You can define multiple models and relationships between them within the same `factory`:

```js
import { factory, primaryKey, oneOf } from '@mswjs/data'
import { random } from 'faker'

const db = factory({
  book: {
    isbn: primaryKey(random.uuid),
    // "book.publisher" is a relational property
    // that references an entity of the "publisher" model.
    publisher: oneOf('publisher')
  },
  publisher: {
    id: primaryKey(random.uuid),
    name: random.words,
  }
})
```

> Learn more about [defining model relationships](https://github.com/mswjs/data).

---

## Seeding

Once the models are defined, create an entity of a particular model by calling the `.create()` method on it:

```js
db.user.create()
```

Creating an entity without any arguments will fill its properties using the value initializers you've specified in the model definition.

The `.create()` method accepts an optional argument that stands for the initial values of the entity. For example, if we wish to create a user with a fixed "firstName" value, we can provide that value in the initial values object:

```js
db.user.create({
  // Uses an exact value for the "firstName" property,
  // while seeding the "id" based on its getter.
  firstName: 'John',
})
```

---

## Querying client

Apart from the data modeling functionality, this library provides a _querying client_ that allows you to find, modify, and delete entities on runtime. The querying client brings Data to life, as it enables dynamic scenarios against the generated data.

Each model supports the following querying methods:

- `.findFirst()`, finds the first entity that matches a query.
- `.findMany()`, finds multiple entities that match a query.
- `.count()`, returns the number of entities for a model.
- `.update()`, updates an entity that matches a query.
- `.updateMany()`, updates multiple entities that match a query.
- `.delete()`, deletes an entity that matches a query.
- `.deleteMany()`, deletes multiple entities that match a query.

The most basic example of querying is finding an entity by its primary key. In our case, we can find a user by its "id" like this:

```js
// Find a user with the given "id" (primary key).
const user = db.user.findFirst({
  where: {
    id: {
      equals: 'abc-123',
    },
  },
})
```

Let's focus on `where` part of the query above. When querying entities, `where` is a predicate object that describes the criteria against an entity. The structure of that predicate is the following:

```js
{
  where: {
    [property]: {
      [operator]: expectedValue
    }
  }
}
```

- `[property]`, a known property of the model. In the case of our "user" model, this can be "id" or "firstName".
- `[operator]`, an operator function name that compares the actual and expected values of the referenced property.

Operators depend on the value type that's being queried. When querying a string (like we do with `where.id`, where "id" is of the String type), operators like `equals`, `notEquals`, `contains`, `in`, and others are available. When querying a number, you have access to the `gt`, `lte`, `between`, etc. operators instead.

> Querying methods are strongly typed, validating the known model properties and value-based operators on build time. Experiment with your models to learn about all the different options at your disposal!

In a similar fashion, we can query multiple entities. Here's how we can get all the users that satisfy a certain age criteria:

```js
// Returns all users whose "user.age"
// is greater or equal 18.
const users = db.user.findMany({
  where: {
    age: {
      gte: 18,
    },
  },
})
```

> Data supports [cursor](https://github.com/mswjs/data#cursor-based-pagination) and [offset](https://github.com/mswjs/data#offset-based-pagination) pagination to work with large data sets.

There is much more functionality that Data provides, so don't hesitate to explore the library API. Refer to the [documentation](https://github.com/mswjs/data) for API definition and usage examples.

---

## Integration with Mock Service Worker

Here's a gigantic cherry on top: you can turn any data model into [request handlers](https://mswjs.io/docs/basics/request-handler) (both REST _and_ GraphQL) to encapsulate its operations, like creation and querying, under the respective API routes.

### Generating REST API

Using our `db` and its "user" model, we can turn it into a REST API "server" in a single command:

```js
// src/mocks/browser.js
import { setupWorker } from 'msw'
// Import the "db" object.
import { db } from './db'

const worker = setupWorker(
  // Generate REST API request handlers
  // based on the "user" model.
  ...db.user.toHandlers('rest')
)

worker.start()
```

> Looks unfamiliar? Learn how to [get started with Mock Service Worker](https://mswjs.io/docs/getting-started/install).

Calling `.toHandlers()` on a model generates CRUD routes for working with that model. In the example above, the following request handlers will be created:

- `GET /users/`, returns all users in the database.
- `GET /users/:id`, returns a user by their primary key (`id`).
- `POST /users`, creates a new user.
- `PUT /users/:id`, updates an existing user.
- `DELETE /users/:id`, deletes a user by their primary key.

> Notice how the model name is pluralized ("user**s**") to reflect the proper semantics when working with the "user" resource.

With the handlers established, you can create and query users in your application as you would do against an actual HTTP server:

```js
// Create a new user in the database.
fetch('/users', {
  method: 'POST',
  // The body is used as the initial entity values.
  body: JSON.stringify({
    id: 'abc-123',
    firstName: 'John',
  }),
})

// Then, query the created user.
fetch('/users/abc-123')
```

Explore this interactive sandbox to learn more about generating REST API handlers from your data models:

{% codesandbox data-rest-api-handlers-i91nv  %}

### Generating GraphQL API

A model can also generate GraphQL handlers:

```js
const worker = setupWorker(...db.user.toHandlers('graphql'))
```

This command generates the following GraphQL schema with its types based on your model:

```graphql
type Query {
  user(where: UserQuery): User
  users(where: UserQuery, take: Int, skip: Int, cursor: ID): [User!]
}

type Mutation {
  createUser(data: UserInput!): User!
  updateUser(where: UserQueryInput!, data: UserInput!): User!
  updateUsers(where: UserQueryInput!, data: UserInput!): [User!]
  deleteUser(where: UserQueryInput!, data: UserInput!): User
  deleteUsers(where: UserQueryInput!, data: UserInput!): [User!]
}
```

> All the generated GraphQL types and their names are based on your model name and properties.

With your model turned into request handlers, you can query its entities as you would usually do in GraphQL. Here's an example that uses Apollo to get a user entity by ID:

```js
import { useQuery, gql } from '@apollo/client'

const GET_USER = gql`
  query GetUser {
    # Hey, it's the same query as in the ".findMany()" method!
    user(where: { id: { equals: "abc-123" } }) {
      firstName
      age
    }
  }
`

useQuery(GET_USER)
```

Explore the GraphQL example on Codesandbox:

{% codesandbox data-graphql-api-handlers-2xc4i %}

---

## Call for Contributors!

Data is a new library that has a long way to go and multiple areas to improve. For instance, these are some of the features we'd love to implement:

- Client-side persistence;
- Server-side rendering support;
- `.createMany()` method to seed multiple entities at once, respecting their relations.

If you would like to learn about data modeling or find this area fascinating, [**join as a contributor**](https://github.com/mswjs/data/issues) and shape the way developers would model their fixtures.

You can also support the project financially by [**sponsoring it on Open Collective**](https://opencollective.com/mswjs), allowing the team to work on bug fixes and stunning new features. Your support will not go unnoticed!

---

## Resources & links

{% github mswjs/data no-readme %}

- [Mock Service Worker](https://github.com/mswjs/msw)
- [Follow on Twitter](https://twitter.com/ApiMocking)