home shape

How to model customer surveys in a graph database


The graph database use-case we are stepping through in this post is the following: In our web application we have several places where a user is led through a survey, where she decides on details for one of our products. Some of the options within the survey depend on previous decisions and some are independent.


  • Configure a new car
  • Configure a new laptop
  • Book extras with your flight (meal, reserve seat etc.)
  • Configure a new complete kitchen
  • Collect customer feedback via logic-jump surveys

We would like to easily offer a generic page which can be seeded with any decision-tree and just plays the Q&A game with the user. We also want to be able to easily create new and modify existing decision trees in case the products change.

Graph Database Data Model

When speaking of trees it directly pops into my mind that a graph database probably is a good solution to store the data in, well every tree is a graph. So in this case the data model is actually pretty simple: First we have questions we would like to ask the user. These questions are modeled as vertices having an attribute query stating the string that should be displayed to the user. In case of a localized application we will not have one query attribute, but one for every language we support (e.g. en, de, jp etc.), for simplicity we now assume our shop is only available in English.

Attached to each question we have a list of possible answers leading the user to the next question, or to the end of the survey. These answers are best realized as the edges connecting everything together. Each answer has again a text attribute that should be presented to the user. Finally we have products which best fit for a user after undergoing the survey.

Let’s now visualize a simple decision tree. We are in an online Avocado Shop and want to give our customer the best Avocado:


Tips and Tricks

If we set up the surveys using a graph all vertices in the graph can be reused in several surveys.

This is especially important if you want to offer the same product in completely different surveys. You do not have to store the product twice, making it easier to update. It is also important if you have overlapping surveys, say for two surveys the starting point is different, but based on some decisions the user will end up in the same dialog for both surveys and can only reach identical products with identical questions.

An example for this is if you are selling and leasing cars to a user. You at first have two sub different dialogs asking the user for selling respectively leasing conditions, but at some point in both dialogs the user finally has to decide on the car. This car selection process is again identical for both surveys.


Sorry I am no UX designer 😉 In general I would require only two API endpoints:

  • showQuestion/:id Which would enter the survey at any point and return the question with it’s set of possible answers (the number of answers at this point is unknown and my UI widget is able to display an arbitrary number of option boxes).
  • showProduct/:id Which will show the product because the user is at the end of her survey.

If I want to hold some user state the UI part is responsible for it, it can for instance maintain a list of questions the user has visited and if the user presses “back” it can just resend showQuestion/:id with the second to last question.


In the first endpoints we need to return a query and all it’s possible answers. Using the data model design from above this means we have to select one question by it’s _id and all its outgoing edges.

In ArangoDB the query to achieve this is the following (including comments):

FOR question IN questions
// Select the question. No worries we use the Primary Index O(1), no looping here ;)
FILTER question._key == @id
// Here we start a subquery to join together all answers into a single array
LET answers = (
  // Now we select all outbound edges from question
  FOR next, answer IN OUTBOUND question answers 
  // We make a projection to only return the text and the id of the next object
  // If we would use localisation this place has to be adjusted
  // We have to select the correct translation
  // Also we use the indicator that if next has a query we are not at the end
    RETURN {nextId: next._key, text: answer.text, isFinal: next.query == null} 
  // Finally we return the query, and the list of possible answers
    query: question.query,
    answers: answers

For the other endpoint we only have to display the product by its id.
This can be done via the following even simpler query:

FOR product IN products
  // Select the Product. No worries we use the Primary Index O(1), no looping here ;)
  FILTER product._key == @id
  RETURN product

Creating an API out of it

ArangoDB also offers a nice little framework called Foxx. This framework can easily wrap both queries into nice API endpoints by only adding a couple of lines of JS to it.
In this example we will use the nice shorthand notations that are offered by ES6.

// First just use our queries:
const questionQuery = `
FOR question IN questions
FILTER question._key == @id
LET answers = (
  FOR next, answer IN OUTBOUND question answers 
    RETURN {nextId: next._key, text: answer.text, isFinal: next.query == null} 
    query: question.query,
    answers: answers

const productQuery = `
FOR product IN products
  FILTER product._key == @id
  RETURN product

// Now define the routes:
router.get("/showQuestion/:id", (req, res) => {
  let cursor = db._query(questionQuery, {id: req.pathParams.id})

router.get("/showProduct/:id", (req, res) => {
  let cursor = db._query(productQuery, {id: req.pathParams.id})

And there we go.

See it in action

Luckily Foxx can also serve static files, which can contain HTML and JS code. This feature is useful if you want to ship a small administration frontend with you Foxx App.

In this post I will misuse it to ship my entire survey-demo widget with it. You can simply install it from github using the following repository: https://github.com/arangodb-foxx/survey-generator

Have fun trying it out or even enhance it for your Product if you like it. It is all Apache 2 Licence (and so is ArangoDB).

Michael Hackstein

Michael is a JavaScript and NoSQL enthusiast. In his spare time he is organising colognejs, the JavaScript user group in Cologne Germany scheduled every second month. In his professional life Michael holds a master degree in Computer Science. As Front End Specialist he is member of the ArangoDB core team, developing the web frontend and graph visualisation for this project. He is conference and user group addicted.


  1. ravi on September 17, 2019 at 2:25 pm

    Hi michael, i want to know how to run and install https://github.com/arangodb-foxx/survey-generator.can you please provide steps,as soon as possible.

  2. Michael on September 20, 2019 at 10:55 am

    Hi Ravi,

    i have added a README to the repository that should explain how to get it up and running.
    Here is the content of it:


    In order to test this App you need to have an ArangoDB running. Now you have two options:

    1. WebUI
    Open the web ui default: https://localhost:8529/.
    Navigate to SERVICES on the left.
    Click on “Add Service”
    On the top tab bar select “GitHub”.
    Fill out the form:
    Repository: “arangodb-foxx/survey-generator”
    Version: “master”
    Click INSTALL
    Fill out:
    Mount point: your choice, e.g. “/survey”
    Run Setup?: check
    Click Install
    Now you see this App deployed, click on it.

    2.Foxx CLI
    Requires Node, install the FoxxCLI:
    Using Yarn: yarn global add foxx-cli
    Or using NPM: npm install –global foxx-cli
    Clone this repository to any path: or download the zip file to
    Now you can run: foxx install “/survey” on either the repo or the zip file.
    Now you App is deployed under /survey.

    If you are using default configuration of arangodb and installed this app under survey you can access it now at: default: https://localhost:8529/_db/_system/survey/index.html.

Leave a Comment

Get the latest tutorials, blog posts and news: