A modern JS app (V.I): updating our GraphQL schema and API for search & pagination

A modern React / Node.js application

Part 5.1: Updating our GraphQL schema and API for search & pagination

Let’s create some data

First thing’s first, let’s generate some properties. We’ll create a script that generates and adds Property objects to our Mongo instance. So let’s create a directory in server/scripts/database called seeds. In the top-level directory, run:

mkdir server/scripts/database/seeds

Inside our new seeds directory, we’ll have two files:

001.do.addProperties.js:


/*
  This script generates N random Property documents by choosing Property
  attributes from the following arrays.

  Seeded Property documents have an id < 0, for easy deletion.
*/
var N = 100

var street1s = [
  'Main St.',
  'Walnut',
  'Pearl St.',
  'Paseo del Prado',
  'Dejvicka',
  'Malirska',
  'Capital of Texas Hwy',
  'Old Main St.',
]

var street2s = [
  'Unit #1',
  'Apt #3A',
  '26A',
  '',
  null,
  '13',
  'E'
]

var cities = [
  'Boulder',
  'Austin',
  'Prague',
  'Munich',
  'San Francisco',
  'Denver',
  'Vail'
]

var states = [
  'Colorado',
  'Texas',
  'Bohemia',
  'California',
  'New York',
  'Alaska'
]

// generate properties
var properties = []
for(var i = 1;i <= N;i++) {
  properties.push({
    id      : -i,
    street1 : street1s[parseInt(Math.random() * street1s.length, 10)],
    street2 : street2s[parseInt(Math.random() * street2s.length, 10)],
    city    : cities[parseInt(Math.random() * cities.length, 10)],
    state   : states[parseInt(Math.random() * states.length, 10)],
    zip     : parseInt(Math.random() * 90000, 10) + 10000
  })
}

// insert randomly generated properties
db.getCollection('Property').insertMany(properties)

001.undo.addProperties.js:

db.getCollection('Property').deleteMany({ id: { $lt: 0 } })

As you can see, the first file simply generates 100 Properties randomly by picking values from each array (street1s, street2s, cities, states), and inserts them into our bnb-book DB hosted on our Mongo instance. Each property has a negative integer as an ID, for easy identification that this is just seed/garbage data.

The second file deletes all Property objects with an id less than 0.

How do we run these files? Where does the db variable come from? Let’s add a command to our server/package.json file:

"db-seed": "mongo bnb-book scripts/database/seeds/*.undo.*.js && mongo bnb-book scripts/database/seeds/*.do.*.js",

So when we run npm run db-seed, we’re piping any seeds/*.undo.*.js files into the mongo command line interface, and then seeds/*.do.*.js files. This helps us keep our seed operations idempotent. We should be able to delete/insert seed data easily and at will.

There’s nothing special about this pattern: we could make our seeding a little more granular and add commands db-seed-do and db-seed-undo but I like the idea of having a single command that will always result in predictable seed data after being run.

Our current GraphQL schema doesn’t provide much in the way pagination and search. Let’s change that. Update our schema.graphql file:

scalar Date

type User {
  id: ID!
  email: String!
  firstName: String
  lastName: String
}

input UserInput {
  email: String
  firstName: String
  lastName: String
}

type LocalAuth {
  id: ID!
  user: User!
  password: String!
}

type Property {
  id: ID!
  owner: User!
  rooms: [Room]
  street1: String!
  street2: String
  city: String!
  state: String!
}

input PropertyInput {
  owner: UserInput
  rooms: [RoomInput]
  street1: String
  street2: String
  city: String
  state: String
}

type Room {
  id: ID!
  name: String!
  price: Float
  description: String
  image: File
}

input RoomInput {
  name: String
  price: Float
  description: String
  image: FileInput
}

type File {
  id: ID!
  url: String!
}

input FileInput {
  url: String
}

type Booking {
  id: ID!
  room: Room!
  email: String!
  start: Date!
  end: Date!
}

input SearchParameters {
  searchText: String
  sortAsc: Boolean
  sortKey: String
  first: Int
  skip: Int
}

type Query {
  fetchUser(id: ID!): User
  fetchProperty(id: ID!): Property
  listProperties(args: PropertyInput, search: SearchParameters) : [Property]
  listRooms(args: RoomInput, search: SearchParameters) : [Room]
}

type Mutation {
  createUser(input: UserInput): User
  updateUser(id: ID!, input: UserInput): User
  deleteUser(id: ID!): User
  createBooking(roomId: ID!, email: String!, start: Date!, end: Date!): Booking
}