3 March 2025

Formity / Expry: The Best Way to Store Forms in Databases

Certain types of applications may require storing forms in a database. For instance, a quiz app might let users create and manage their own quizzes, while a social network could allow each group to define a custom onboarding form to collect specific member information.

What is the capital of Australia?

Choose the answer you think is correct.

SydneyMelbourneCanberra

The Challenge of Storing Forms

Building an application like this requires a structured way to represent forms that can be stored in a database. A JSON-based representation is a great approach, but it comes with challenges - most notably, the need for an algorithm to convert JSON into functional JavaScript code.

The complexity grows as forms become more dynamic. Consider a multi-step form with conditional logic, where questions adapt based on previous answers. To achieve this, you would need to define a JSON structure capable of handling various logical conditions, including comparison, boolean and arithmetic operators. Managing this manually can be both time-consuming and error-prone.

The Solution: Formity / Expry

This is where Expry simplifies the process. Expry enables you to define logic using a structured JSON syntax and seamlessly convert it into JavaScript. You can either create custom mappings or leverage built-in operators to handle logic efficiently.

Even better, Expry provides first-class support for Formity, making it effortless to build advanced multi-step forms with conditional logic - all while keeping the JSON structure clean, maintainable, and free of unnecessary complexity.

Key Features of Expry

Expry offers a powerful set of features, making it the go-to tool for creating JSON-based logic.

Unlimited Possibilities

Define an unlimited number of JSON-based operators to execute any logic you want. With Expry, the possibilities are endless.

Built-in Operators

Expry provides a package with essential operators, including arithmetic, conditional and comparison operators, so you don't have to build them from scratch.

First-class Support for Formity

Expry provides a dedicated package with Formity-specific operators. Using this package, you can significantly simplify the creation of multi-step forms.

How It Works

Expry consists of multiple packages. The main package offers a way to map JSON to JavaScript code but lacks built-in operators. To define operators, we use the createExpry function, which returns a function for executing JSON-based logic.

import { createExpry, Executions } from "@expry/system";

type Operations = {
  map: {
    params: { input: unknown; as: unknown; in: unknown };
    return: unknown[];
  };
  add: {
    params: unknown[];
    return: unknown;
  };
};

const operations: Executions<Operations> = {
  map(args, vars, expry) {
    const array = expry(args.input, vars) as unknown[];
    const as = expry(args.as, vars) as string;
    return array.map((value) => {
      return expry(args.in, { ...vars, [`$${as}`]: value });
    });
  },
  add: (args, vars, expry) => {
    const array = args.map((num) => expry(num, vars)) as number[];
    return array.reduce((acc, val) => acc + val, 0);
  },
};

const expry = createExpry<[Operations]>(operations);

const expression: unknown = {
  $map: {
    input: [1, 2, 3],
    as: "num",
    in: { $add: ["$$num", 1] },
  },
};

const result = expry(expression);

console.log(result); // [2, 3, 4]

To use built-in operators, we can include an additional package that provides a wide range of operators, such as arithmetic, comparison, and array operators.

import { createExpry } from "@expry/system";

import { basicOperations, BasicOperations } from "@expry/basic";

const expry = createExpry<[BasicOperations]>(basicOperations);

const expression: unknown = {
  name: { $concat: ["$name", " ", "$surname"] },
  mostFavoriteSports: {
    $filter: {
      input: "$sports",
      as: "sport",
      cond: { $gt: ["$$sport.rating", 8] },
    },
  },
};

const variables: Record<string, unknown> = {
  name: "John",
  surname: "Doe",
  sports: [
    { name: "football", rating: 9 },
    { name: "basketball", rating: 10 },
    { name: "tennis", rating: 5 },
    { name: "swimming", rating: 7 },
  ],
};

const result = expry(expression, variables);

console.log(result);

While Expry is versatile, one common use case is forms. To support this, Expry offers a package with Formity operators that streamline multi-step form creation.

import { Schema, Return } from "@formity/react";

import { createExpry } from "@expry/system";
import { basicOperations, BasicOperations } from "@expry/basic";
import { formityOperations, FormityOperations } from "@expry/formity";

import { componentsOperations, ComponentsOperations } from "./components";
import { zodOperations, ZodOperations } from "./zod";

type Operations = [
  BasicOperations,
  FormityOperations,
  ComponentsOperations,
  ZodOperations,
];

const expry = createExpry<Operations>(
  basicOperations,
  formityOperations,
  componentsOperations,
  zodOperations,
);

export type Values = [Return<unknown>];

export const schema = expry([
  {
    $schema$form: {
      // ...
    },
  },
  {
    $schema$return: {
      // ...
    },
  },
]) as Schema<Values>;

Why Choose Expry?

Expry simplifies the challenges of working with JSON-based logic, making it particularly useful for applications that require storing forms in databases. With its flexible architecture, Expry enables you to implement dynamic logic effortlessly.