Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
03 Sep 2025 · 5 min read ·Article 65 / 125
Go

65 Dynamic Schemas and Custom Scalar Types

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

65 Dynamic Schemas and Custom Scalar Types: Exploring a More Flexible Type System

In the modern world of software development, an API is not merely a bridge between systems, but also a contract that defines how data is exchanged. One of the main pillars in defining an API, especially in the context of GraphQL or gRPC, is the type system. This article explores two advanced aspects: dynamic schemas and custom scalar types. We will look at the reasons behind them, their implementation, and practical examples of how to use them both.


Why Dynamic Schemas?

In many cases, business needs can change rapidly. If an API enforces a rigid data structure that is hard to adapt, even small changes can lead to massive deployments. This is where the concept of dynamic schemas comes in—where the data structure can be adjusted or even defined by API consumers without having to hardcode it at the source-code level.

Dynamic Schemas make an API:

  • Easier to adapt
  • Have minimal downtime from deploying a new schema
  • Support custom use cases tailored to specific needs

Example Scenario of a Dynamic Schema

Imagine a SaaS application that offers custom data storage for each client. Each tenant wants to store data with a different structure (e.g., HR, Inventory, or CRM). It would be extremely burdensome if we had to create a new schema for every tenant.


A Quick Look at Custom Scalar Types

Scalar data types are the most basic types, such as Int, Float, String, and so on. However, business data types are often far richer, and using basic types can open up the risk of invalid data—for example, storing an email as a String.

This is where custom scalar types play their role:

  • Making the codebase more type safe
  • Simplifying validation on the backend side
  • Making API documentation clearer

Simulation: Dynamic Schema & Custom Scalars in GraphQL

Let’s dive into a real example! We will use GraphQL, which is indeed very flexible both for defining schemas dynamically and for supporting custom scalar types.

1. Creating a Custom Scalar Type

Suppose our application needs special validation for a PhoneNumber type.

javascript
 1// src/schema/scalars.js
 2const { GraphQLScalarType, Kind } = require('graphql');
 3
 4const PhoneNumber = new GraphQLScalarType({
 5  name: 'PhoneNumber',
 6  description: 'Custom Phone Number Scalar',
 7  serialize(value) {
 8    return value; // Assume the value is already a valid string
 9  },
10  parseValue(value) {
11    if (!/^\+628[0-9]{8,}$/.test(value)) {
12      throw new Error('Invalid phone number. Must start with +628');
13    }
14    return value;
15  },
16  parseLiteral(ast) {
17    if (ast.kind === Kind.STRING && /^\+628[0-9]{8,}$/.test(ast.value)) {
18      return ast.value;
19    }
20    throw new Error('Invalid phone number');
21  },
22});
23
24module.exports = { PhoneNumber };

With this approach, any field of type PhoneNumber will always be validated.

2. Defining a Dynamic Schema

GraphQL typically uses a static schema, but there are strategies that allow the schema to change at runtime:

Implementing a Dynamic Schema

Let’s say a client is allowed to decide for themselves which fields they want to store on a Customer object. For example:

javascript
 1// src/schema/dynamicSchema.js
 2const { 
 3  GraphQLObjectType, 
 4  GraphQLSchema, 
 5  GraphQLString,
 6  GraphQLScalarType 
 7} = require('graphql');
 8
 9// Dynamic function generates new fields:
10function generateCustomerType(fieldDefinitions) {
11  const fields = {};
12  fieldDefinitions.forEach(def => {
13    fields[def.name] = { type: GraphQLString, description: def.desc };
14  });
15  return new GraphQLObjectType({
16    name: 'Customer',
17    fields,
18  });
19}
20
21const dynamicFieldDefinitions = [
22  { name: 'firstName', desc: 'Customer First Name' },
23  { name: 'lastName', desc: 'Customer Last Name' },
24  { name: 'phone', desc: 'Phone Number', type: 'PhoneNumber' },
25];
26
27const CustomerType = generateCustomerType(dynamicFieldDefinitions);
28
29const QueryType = new GraphQLObjectType({
30  name: 'Query',
31  fields: {
32    customer: {
33      type: CustomerType,
34      resolve: () => ({ firstName: 'Ahmad', lastName: 'Yani', phone: '+62812345678' }),
35    }
36  }
37});
38
39const schema = new GraphQLSchema({ query: QueryType });
40
41module.exports = { schema };

With this approach, the Customer structure can change simply by modifying the dynamicFieldDefinitions array—there is no need to edit the schema code manually.


Advantages & Challenges

AdvantagesChallenges
Highly flexible, quick to adaptIncreases the risk of runtime errors
Supports multi-tenant / custom dataHarder to validate statically
Better validation via custom scalarsRisk of data leaks without a strict schema
Schema can be changed without re-deployCode becomes increasingly complex

Flow Diagram: The Dynamic Schema Creation Process

MERMAID
flowchart TD
    A[Inisiasi aplikasi] --> B{Ada perubahan struktur data?}
    B -- "Ya" --> C[Muat/Generate Field Definitions dari DB/config]
    B -- "Tidak" --> D[Gunakan Schema yang sudah ada]
    C --> E[Bangun ulang GraphQL Schema di runtime]
    E --> F[Jalankan API Server]
    D --> F

Case Study: Multi-Tenant SaaS

A SaaS wants each tenant to be able to customize the Lead field. Tenant A wants leadStatus, while tenant B wants industry & budget. With a dynamic schema generator, an admin can determine which fields to use:

Example Tenant Configuration:

TenantFieldType
AlphaleadStatusString
BetaindustryString
BetabudgetInt
GammaphonePhoneNumber

The schema is generated based on the configuration table/DB.


Best Practices & Tips

  1. Store dynamic schema definitions outside the code (DB/file store), do not hardcode them.
  2. Custom scalars must be supported well on both the client and the server, for example via documentation.
  3. Always log runtime schema errors to prevent silent bugs.
  4. Use a generator/type factory so that schema creation stays DRY.
  5. If scalability matters, cache the generated schema so that startup is faster.

Closing

By applying dynamic schemas and custom scalar types, your API architecture becomes far more adaptive, easier to evolve, and more robust in terms of data security. However, this extra flexibility comes with a trade-off: increased complexity and the risk of runtime errors that must be managed carefully.

With careful design and implementation, these two concepts can become the key differentiator between a scalable SaaS product or API and one that easily deadlocks on change.

As always, the key is experimentation and review: don’t hesitate to try, but be disciplined in validation and monitoring. Happy building the API of the future! 🚀

Related Articles

💬 Comments