Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

Page updated Apr 29, 2024

Warehouse Management System

In this "Warehouse management system" example, you will learn how to configure common access patterns for your app. This example has the following types:

  • Warehouse
  • Product
  • Inventory
  • Employee
  • AccountRepresentative
  • Customer

These types have the following common access patterns:

  1. Look up employee details by employee ID
  2. Query employee details by employee name
  3. Find an employee's phone number(s)
  4. Find a customer's phone number(s)
  5. Get orders for a given customer within a given date range
  6. Show all open orders within a given date range across all customers
  7. See all employees recently hired
  8. Find all employees working in a given warehouse
  9. Get all items on order for a given product
  10. Get current inventories for a given product at all warehouses
  11. Get customers by account representative
  12. Get orders by account representative and date
  13. Get all items on order for a given product
  14. Get all employees with a given job title
  15. Get inventory by product and warehouse
  16. Get total product inventory
  17. Get account representatives ranked by order total and sales period

The following schema introduces the required indexes and relationships so that you can support these access patterns:

# This "input" configures a global authorization rule to enable public access to
# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql/auth
input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY!
type Order @model {
id: ID!
customerID: ID! @index(name: "byCustomerByStatusByDate", sortKeyFields: ["status", "date"]) @index(name: "byCustomerByDate", sortKeyFields: ["date"])
accountRepresentativeID: ID! @index(name: "byRepresentativebyDate", sortKeyFields: ["date"])
productID: ID! @index(name: "byProduct", sortKeyFields: ["id"])
status: String!
amount: Int!
date: String!
}
type Customer @model {
id: ID!
name: String!
phoneNumber: String
accountRepresentativeID: ID! @index(name: "byRepresentative", sortKeyFields: ["id"])
ordersByDate: [Order] @hasMany(indexName: "byCustomerByDate", fields: ["id"])
ordersByStatusDate: [Order] @hasMany(indexName: "byCustomerByStatusByDate", fields: ["id"])
}
type Employee @model {
id: ID!
name: String! @index(name: "byName", queryField: "employeeByName", sortKeyFields: ["id"])
startDate: String!
phoneNumber: String!
warehouseID: ID! @index(name: "byWarehouse", sortKeyFields: ["id"])
jobTitle: String! @index(name: "byTitle", queryField: "employeesByJobTitle", sortKeyFields: ["id"])
newHire: String! @index(name: "newHire", queryField: "employeesNewHire", sortKeyFields: ["id"]) @index(name: "newHireByStartDate", queryField: "employeesNewHireByStartDate", sortKeyFields: ["startDate"])
}
type Warehouse @model {
id: ID!
employees: [Employee] @hasMany(indexName: "byWarehouse", fields: ["id"])
}
type AccountRepresentative @model {
id: ID!
customers: [Customer] @hasMany(indexName: "byRepresentative", fields: ["id"])
orders: [Order] @hasMany(indexName: "byRepresentativebyDate", fields: ["id"])
orderTotal: Int
salesPeriod: String @index(name: "bySalesPeriodByOrderTotal", queryField: "repsByPeriodAndTotal", sortKeyFields: ["orderTotal"])
}
type Inventory @model {
productID: ID! @primaryKey(sortKeyFields: ["warehouseID"])
warehouseID: ID! @index(name: "byWarehouseID", queryField: "itemsByWarehouseID")
inventoryAmount: Int!
}
type Product @model {
id: ID!
name: String!
orders: [Order] @hasMany(indexName: "byProduct", fields: ["id"])
inventories: [Inventory] @hasMany(fields: ["id"])
}

Now that you have the schema created, let's create the items in the database that you will be operating against:

# first
mutation createWarehouse {
createWarehouse(input: {id: "1"}) {
id
}
}
# second
mutation createEmployee {
createEmployee(input: {
id: "amanda"
name: "Amanda",
startDate: "2018-05-22",
phoneNumber: "6015555555",
warehouseID: "1",
jobTitle: "Manager",
newHire: "true"}
) {
id
jobTitle
name
newHire
phoneNumber
startDate
warehouseID
}
}
# third
mutation createAccountRepresentative {
createAccountRepresentative(input: {
id: "dabit"
orderTotal: 400000
salesPeriod: "January 2019"
}) {
id
orderTotal
salesPeriod
}
}
# fourth
mutation createCustomer {
createCustomer(input: {
id: "jennifer_thomas"
accountRepresentativeID: "dabit"
name: "Jennifer Thomas"
phoneNumber: "+16015555555"
}) {
id
name
accountRepresentativeID
phoneNumber
}
}
# fifth
mutation createProduct {
createProduct(input: {
id: "yeezyboost"
name: "Yeezy Boost"
}) {
id
name
}
}
# sixth
mutation createInventory {
createInventory(input: {
productID: "yeezyboost"
warehouseID: "1"
inventoryAmount: 300
}) {
productID
inventoryAmount
warehouseID
}
}
# seventh
mutation createOrder {
createOrder(input: {
amount: 300
date: "2018-07-12"
status: "pending"
accountRepresentativeID: "dabit"
customerID: "jennifer_thomas"
productID: "yeezyboost"
}) {
id
customerID
accountRepresentativeID
amount
date
customerID
productID
}
}

1. Look up employee details by employee ID

This can simply be done by querying the employee model with an employee ID, no @primaryKey or @index need to be explicitly specified to make this work.

query getEmployee($id: ID!) {
getEmployee(id: $id) {
id
name
phoneNumber
startDate
jobTitle
}
}

2. Query employee details by employee name

The @index byName on the Employee type makes this access-pattern feasible because under the hood an index is created and a query is used to match against the name field. You can use this query:

query employeeByName($name: String!) {
employeeByName(name: $name) {
items {
id
name
phoneNumber
startDate
jobTitle
}
}
}

3. Find an Employee’s phone number

Either one of the previous queries would work to find an employee’s phone number as long as one has their ID or name.

4. Find a customer’s phone number

A similar query to those given above but on the Customer model would give you a customer’s phone number.

query getCustomer($customerID: ID!) {
getCustomer(id: $customerID) {
phoneNumber
}
}

5. Get orders for a given customer within a given date range

There is a one-to-many relation that lets all the orders of a customer be queried.

This relationship is created by having the @index name byCustomerByDate on the Order model that is queried by the @hasMany relationship on the orders field of the Customer model.

A sort key with the date is used. What this means is that the GraphQL resolver can use predicates like Between to efficiently search the date range rather than scanning all records in the database and then filtering them out.

The query one would need to get the orders to a customer within a date range would be:

query getCustomerWithOrdersByDate($customerID: ID!) {
getCustomer(id: $customerID) {
ordersByDate(date: {
between: [ "2018-01-22", "2020-10-11" ]
}) {
items {
id
amount
productID
}
}
}
}

6. Show all open orders within a given date range across all customers

The @index byCustomerByStatusByDate enables you to run a query that would work for this access pattern.

In this example, a composite sort key (combination of two or more keys) with the status and date is used. What this means is that the unique identifier of a record in the database is created by concatenating these two fields (status and date) together, and then the GraphQL resolver can use predicates like between or contains to efficiently search the unique identifier for matches rather than scanning all records in the database and then filtering them out.

query listCustomersWithOrdersByStatusDate {
listCustomers {
items {
ordersByStatusDate(statusDate: {
between: [
{ status: "pending", date: "2018-01-22" },
{ status: "pending", date: "2020-10-11" }
]}) {
items {
id
amount
date
}
}
}
}
}

7. See all employees hired recently

Having @index(name: "newHire", fields: ["newHire", "id"]) on the Employee model allows one to query by whether an employee has been hired recently.

query employeesNewHire {
employeesNewHire(newHire: "true") {
items {
id
name
phoneNumber
startDate
jobTitle
}
}
}

You can also query and have the results returned by start date by using the employeesNewHireByStartDate query:

query employeesNewHireByDate {
employeesNewHireByStartDate(newHire: "true") {
items {
id
name
phoneNumber
startDate
jobTitle
}
}
}

8. Find all employees working in a given warehouse

This needs a one to many relationship from warehouses to employees. As can be seen from the @hasMany relationship in the Warehouse model, this relationship uses the byWarehouse index on the Employee model. The relevant query would look like this:

query getWarehouse($warehouseID: ID!) {
getWarehouse(id: $warehouseID) {
id
employees{
items {
id
name
startDate
phoneNumber
jobTitle
}
}
}
}

9. Get all items on order for a given product

This access-pattern would use a one-to-many relation from products to orders. With this query you can get all orders of a given product:

query getProductOrders($productID: ID!) {
getProduct(id: $productID) {
id
orders {
items {
id
status
amount
date
}
}
}
}

10. Get current inventories for a product at all warehouses

The query needed to get the inventories of a product in all warehouses would be:

query getProductInventoryInfo($productID: ID!) {
getProduct(id: $productID) {
id
inventories {
items {
warehouseID
inventoryAmount
}
}
}
}

11. Get customers by account representative

This uses a has-many relationship between account representatives and customers:

The query needed would look like this:

query getCustomersForAccountRepresentative($representativeId: ID!) {
getAccountRepresentative(id: $representativeId) {
customers {
items {
id
name
phoneNumber
}
}
}
}

12. Get orders by account representative and date

As can be seen in the AccountRepresentative model this relationship uses the byRepresentativebyDate field on the Order model to create the connection needed. The query needed would look like this:

query getOrdersForAccountRepresentative($representativeId: ID!) {
getAccountRepresentative(id: $representativeId) {
id
orders(date: {
between: [
"2010-01-22", "2020-10-11"
]
}) {
items {
id
status
amount
date
}
}
}
}

13. Get all items on order for a given product

This is the same as number 9.

14. Get all employees with a given job title

Using the byTitle @index makes this access pattern quite easy.

query employeesByJobTitle {
employeesByJobTitle(jobTitle: "Manager") {
items {
id
name
phoneNumber
jobTitle
}
}
}

15. Get inventory by product by warehouse

Here having the inventories be held in a separate model is particularly useful since this model can have its own partition key and sort key such that the inventories themselves can be queried as is needed for this access-pattern.

A query on this model would look like this:

query inventoryByProductAndWarehouse($productID: ID!, $warehouseID: ID!) {
getInventory(productID: $productID, warehouseID: $warehouseID) {
productID
warehouseID
inventoryAmount
}
}

You can also get all inventory from an individual warehouse by using the itemsByWarehouseID query created by the byWarehouseID key:

query byWarehouseId($warehouseID: ID!) {
itemsByWarehouseID(warehouseID: $warehouseID) {
items {
inventoryAmount
productID
}
}
}

16. Get total product inventory

How this would be done depends on the use case. If one just wants a list of all inventories in all warehouses, one could just run a list inventories on the Inventory model:

query listInventorys {
listInventorys {
items {
productID
warehouseID
inventoryAmount
}
}
}

17. Get sales representatives ranked by order total and sales period

The sales period is either a date range or maybe even a month or week. Therefore you can set the sales period as a string and query using the combination of salesPeriod and orderTotal. You can also set the sortDirection in order to get the return values from largest to smallest:

query repsByPeriodAndTotal {
repsByPeriodAndTotal(
sortDirection: DESC,
salesPeriod: "January 2019",
orderTotal: {
ge: 1000
}) {
items {
id
orderTotal
}
}
}