@mswjs/data
Data modeling and relation library for testing JavaScript applications.
Motivation
When testing API interactions you often need to mock data. Instead of keeping a hard-coded set of fixtures, this library provides you with must-have tools for data-driven API mocking:
- An intuitive interface to model data;
- The ability to create relationships between models;
- The ability to query data in a manner similar to an actual database.
Getting started
Install
$ npm install @mswjs/data --save-dev
# or
$ yarn add @mswjs/data --dev
Describe data
With this library, you're modeling data using the factory
function. That function accepts an object where each key represents a model name and the values are model definitions. A model definition is an object where the keys represent model properties and the values are value getters.
// src/mocks/db.js
import { factory, primaryKey } from '@mswjs/data'
export const db = factory({
// Create a "user" model,
user: {
// ...with these properties and value getters.
id: primaryKey(() => 'abc-123'),
firstName: () => 'John',
lastName: () => 'Maverick',
},
})
See the Recipes for more guidelines on data modeling.
Throughout this document native JavaScript constructors (i.e. String, Number) will be used as values getters for the models, as they both create a value and define its type. In practice, you may consider using value generators or tools like Faker for value getters.
Using the primary key
Each model must have a primary key. That is a root-level property representing the model's identity. Think of it as an "id" column for a particular table in a database.
Declare a primary key by using the primaryKey
function:
import { factory, primaryKey } from '@mswjs/data'
factory({
user: {
id: primaryKey(String),
},
})
In the example above, the id
is the primary key for the user
model. This means that whenever a user
is created it must have the id
property that equals a unique String
. Any property can be marked as a primary key, it doesn't have to be named "id".
Just like regular model properties, the primary key accepts a getter function that you can use to generate its value when creating entities:
import { faker } from '@faker-js/faker'
factory({
user: {
id: primaryKey(faker.datatype.uuid),
},
})
Each time a new
user
is created, itsuser.id
property is seeded with the value returned from thedatatype.uuid
function call.
Once your data is modeled, you can use Model methods to interact with it (create/update/delete). Apart from serving as interactive, queryable fixtures, you can also integrate your data models into API mocks to supercharge your prototyping/testing workflow.
API
factory
The factory
function is used to model a database. It accepts a model dictionary and returns an API to interact with the described models.
import { factory, primaryKey } from '@mswjs/data'
const db = factory({
user: {
id: primaryKey(String),
firstName: String,
age: Number,
},
})
Learn more about the Model methods and how you can interact with the described models.
Each factory
call encapsulates an in-memory database instance that holds the respective models. It's possible to create multiple database instances by calling factory
multiple times. The entities and relationships, however, are not shared between different database instances.
primaryKey
Marks the property of a model as a primary key.
import { factory, primaryKey } from '@mswjs/data'
const db = factory({
user: {
id: primaryKey(String),
},
})
// Create a new "user" with the primary key "id" equal to "user-1".
db.user.create({ id: 'user-1' })
Primary key must be unique for each entity and is used as the identifier to query a particular entity.
nullable
Marks the current model property as nullable.
import { factory, primaryKey, nullable } from '@mswjs/data'
factory({
user: {
id: primaryKey(String)
// "user.title" is a nullable property.
title: nullable(String)
}
})
Learn more how to work with Nullable properties.
oneOf
Creates a *-to-one
relationship with another model.
import { factory, primaryKey, oneOf } from '@mswjs/data'
factory({
user: {
id: primaryKey(String),
role: oneOf('userGroup'),
},
userGroup: {
name: primaryKey(String),
},
})
Learn more about Modeling relationships.
manyOf
Creates a *-to-many
relationship with another model.
import { factory, primaryKey, manyOf } from '@mswjs/data'
factory({
user: {
id: primaryKey(String),
publications: manyOf('post'),
},
post: {
id: primaryKey(String),
title: String,
},
})
Learn more about Modeling relationships.
drop
Deletes all entities in the given database instance.
import { factory, drop } from '@mswjs/data'
const db = factory(...models)
drop(db)
Model methods
Each model has the following methods:
create()
findFirst()
findMany()
count()
getAll()
update()
updateMany()
delete()
deleteMany()
toHandlers()
create
Creates an entity for the model.
const user = db.user.create()
When called without arguments, .create()
will populate the entity properties using the getter functions you've specified in the model definition.
You can also provide a partial initial values when creating an entity:
const user = db.user.create({
firstName: 'John',
})
Note that all model properties are optional, including relational properties.
findFirst
Returns the first entity that satisfies the given query.
const user = db.user.findFirst({
where: {
id: {
equals: 'abc-123',
},
},
})
findMany
Returns all the entities that satisfy the given query.
const users = db.user.findMany({
where: {
followersCount: {
gte: 1000,
},
},
})
count
Returns the number of records for the given model.
db.user.create()
db.user.create()
db.user.count() // 2
Can accept an optional query argument to filter the records before counting them.
db.user.count({
where: {
role: {
equals: 'reader',
},
},
})
getAll
Returns all the entities of the given model.
const allUsers = db.user.getAll()
update
Updates the first entity that matches the query.
const updatedUser = db.user.update({
// Query for the entity to modify.
where: {
id: {
equals: 'abc-123',
},
},
// Provide partial next data to be
// merged with the existing properties.
data: {
// Specify the exact next value.
firstName: 'John',
// Alternatively, derive the next value from
// the previous one and the unmodified entity.
role: (prevRole, user) => reformatRole(prevRole),
},
})
updateMany
Updates multiple entities that match the query.
const updatedUsers = db.user.updateMany({
// Query for the entity to modify.
where: {
id: {
in: ['abc-123', 'def-456'],
},
},
// Provide partial next data to be
// merged with the existing properties.
data: {
firstName: (firstName) => firstName.toUpperCase(),
},
})
delete
Deletes the entity that satisfies the given query.
const deletedUser = db.user.delete({
where: {
followersCount: {
equals: 0,
},
},
})
deleteMany
Deletes multiple entities that match the query.
const deletedUsers = db.user.deleteMany({
where: {
followersCount: {
lt: 10,
},
},
})
toHandlers
Generates request handlers for the given model to use with Mock Service Worker. All generated handlers are automatically connected to the respective model methods, enabling you to perform CRUD operations against your mocked database.
REST handlers
import { factory, primaryKey } from '@mswjs/data'
const db = factory({
user: {
id: primaryKey(String),
firstName: String,
},
})
// Generates REST API request handlers.
db.user.toHandlers('rest')
- Learn more about REST API mocking integration.
GraphQL handlers
import { factory, primaryKey } from '@mswjs/data'
const db = factory({
user: {
id: primaryKey(String),
firstName: String,
},
})
// Generates GraphQL API request handlers.
db.user.toHandlers('graphql')
- Learn more about GraphQL API mocking integration.
Scoping handlers
The .toHandlers()
method supports an optional second baseUrl
argument to scope the generated handlers to a given endpoint:
db.user.toHandlers('rest', 'https://example.com')
db.user.toHandlers('graphql', 'https://example.com/graphql')
Recipes
- Modeling:
- Querying:
Nullable properties
By default, all model properties are non-nullable. You can use the nullable
function to mark a property as nullable:
import { factory, primaryKey, nullable } from '@mswjs/data'
const db = factory({
user: {
id: primaryKey(String),
firstName: String,
// "user.age" is a nullable property.
age: nullable(Number),
},
})
db.user.create({
id: 'user-1',
firstName: 'John',
// Nullable properties can be explicit null as the initial value.
age: null,
})
db.user.update({
where: {
id: {
equals: 'user-1',
},
},
data: {
// Nullable properties can be updated to null.
age: null,
},
})
You can define Nullable relationships in the same manner.
When using Typescript, you can manually set the type of the property when it cannot be otherwise inferred from the seeding function, such as when you want a property to default to null
:
import { factory, primaryKey, nullable } from '@mswjs/data'
const db = factory({
user: {
id: primaryKey(String),
age: nullable<number>(() => null),
},
})
Nested structures
You may use nested objects to design a complex structure of your model:
import { factory, primaryKey, nullable } from '@mswjs/data'
const db = factory({
user: {
id: primaryKey(String),
address: {
billing: {
street: String,
city: nullable(String),
},
},
},
})
// You can then create and query your data
// based on the nested properties.
db.user.create({
id: 'user-1',
address: {
billing: {
street: 'Baker st.',
city: 'London',
},
},
})
db.user.update({
where: {
id: {
equals: 'user-1',
},
},
data: {
address: {
billing: {
street: 'Sunwell ave.',
city: null,
},
},
},
})
Note that you cannot mark a nested property as the primary key.
You may also specify relationships nested deeply in your model:
factory({
user: {
id: primaryKey(String),
address: {
billing: {
country: oneOf('country'),
},
},
},
country: {
code: primaryKey(String),
},
})
Learn more about Model relationships.
Model relationships
Relationship is a way for a model to reference another model.
One-to-One
import { factory, primaryKey, oneOf } from '@mswjs/data'
const db = factory({
user: {
id: primaryKey(String),
firstName: String,
},
post: {
id: primaryKey(String),
title: String,
// The "post.author" references a "user" model.
author: oneOf('user'),
},
})
const user = db.user.create({ firstName: 'John' })
const post = db.post.create({
title: 'My journey',
// Use a "user" entity as the actual value of this post's author.
author: user,
})
post.author.firstName // "John"
One-to-Many
import { factory, primaryKey, manyOf } from '@mswjs/data'
const db = factory({
user: {
id: primaryKey(String),
// "user.posts" is a list of the "post" entities.
posts: manyOf('post'),
},
post: {
id: primaryKey(String),
title: String,
},
})
const posts = [
db.post.create({ title: 'First' }),
db.post.create({ title: 'Second' }),
]
const user = db.user.create({
// Assign the list of existing posts to this user.
posts,
})
user.posts // [{ title: "First" }, { title: "Second" }]
Many-to-One
import { factory, primaryKey, oneOf } from '@mswjs/data'
const db = factory({
country: {
name: primaryKey(String),
},
user: {
id: primaryKey(String),
country: oneOf('country'),
},
car: {
serialNumber: primaryKey(String),
country: oneOf('country'),
},
})
const usa = db.country.create({ name: 'The United States of America' })
// Create a "user" and a "car" with the same country.
db.user.create({ country: usa })
db.car.create({ country: usa })
Unique relationships
Both oneOf
and manyOf
relationships may be marked as unique. A unique relationship is where a referenced entity cannot be assigned to another entity more than once.
In the example below we define the "user" and "invitation" models, and design their relationship so that one invitation cannot be assigned to multiple users.
import { factory, primaryKey, oneOf } from '@mswjs/data'
const db = factory({
user: {
id: primaryKey(String),
invitation: oneOf('invitation', { unique: true }),
},
invitation: {
id: primaryKey(String),
},
})
const invitation = db.invitation.create()
const john = db.user.create({ invitation })
// Assigning the invitation already used by "john"
// will throw an exception when creating this entity.
const karl = db.user.create({ invitation })
Nullable relationships
Both oneOf
and manyOf
relationships may be passed to nullable
to allow
instantiating and updating that relation to null.
import { factory, primaryKey, oneOf, nullable } from '@mswjs/data'
const db = factory({
user: {
id: primaryKey(String),
invitation: nullable(oneOf('invitation')),
friends: nullable(manyOf('user')),
},
invitation: {
id: primaryKey(String),
},
})
const invitation = db.invitation.create()
// Nullable relationships are instantiated with null.
const john = db.user.create({ invitation }) // john.friends === null
const kate = db.user.create({ friends: [john] }) // kate.invitation === null
db.user.updateMany({
where: {
id: {
in: [john.id, kate.id],
},
},
data: {
// Nullable relationships can be updated to null.
invitation: null,
friends: null,
},
})
Querying data
This library supports querying of the seeded data similar to how one would query a SQL database. The data is queried based on its properties. A query you construct depends on the value type you are querying.
String operators
equals
notEquals
contains
notContains
in
notIn
Number operators
equals
notEquals
gt
gte
lt
lte
between
notBetween
in
notIn
Boolean operators
equals