Introduction
Choosing an ORM (Object-Relational Mapping) for your application is often a balance between performance, flexibility, and developer experience.
Drizzle ORMhas become a popular option for developers using relational databases to back JavaScript and TypeScript applications. The lightweight nature of this TypeScript-first framework optimizes performance and productivity without sacrificing type safety. Drizzle provides best-in-class feature completeness through dialect-specific packages. This allows developers to work with PostgreSQL and PostgreSQL-compatible databases like YugabyteDB.
In this blog, I’ll demonstrate how you can use Drizzle ORM to develop an IoT application with YugabyteDB. Structured and semi-structured (JSONB) data can be stored in the same database, making YugabyteDB the perfect choice for supporting heterogenous or evolving datasets.
Getting Started
Before setting up a TypeScript project with Drizzle, I’ll create a 3-node YugabyteDB cluster in Docker.
docker run -d --name yugabytedb-node1 --net test_net \
-p 15433:15433 -p 5433:5433 \
-v ~/yugabyte-volume/node1:/home/yugabyte/yb_data --restart unless-stopped \
yugabytedb/yugabyte:latest \
bin/yugabyted start
--base_dir=/home/yugabyte/yb_data --background=false \
--cloud_location=gcp.us-east1.us-east1-a \
--fault_tolerance=region
while ! docker exec -it yugabytedb-node1 postgres/bin/pg_isready -U yugabyte -h yugabytedb-node1; do sleep 1; done
docker run -d --name yugabytedb-node2 --net test_net \
-p 15434:15433 -p 5434:5433 \
-v ~/yugabyte-volume/node2:/home/yugabyte/yb_data --restart unless-stopped \
yugabytedb/yugabyte:latest \
bin/yugabyted start --join=yugabytedb-node1 --base_dir=/home/yugabyte/yb_data --background=false \
--cloud_location=gcp.us-central1.us-central1-a \
--fault_tolerance=region
docker run -d --name yugabytedb-node3 --net test_net \
-p 15435:15433 -p 5435:5433 \
-v ~/yugabyte-volume/node3:/home/yugabyte/yb_data --restart unless-stopped \
yugabytedb/yugabyte:latest \
bin/yugabyted start --join=yugabytedb-node1 --base_dir=/home/yugabyte/yb_data --background=false \
--cloud_location=gcp.us-west2.us-west2-a \
--fault_tolerance=region
This cluster can be configured to be fault-tolerant across regions. This means the data is replicated across regions allowing the cluster to withstand a full regional outage.
Here’s how to use the yugabyted command-line utility to configure the data placement for the cluster.
docker exec -it yugabytedb-node1 \
bin/yugabyted configure data_placement --fault_tolerance=region --base_dir=/home/yugabyte/yb_data
+-----------------------------------------------------------------------------------------------+
| yugabyted |
+-----------------------------------------------------------------------------------------------+
| Status : Configuration successful. Primary data placement is geo-redundant. |
| Fault Tolerance : Primary Cluster can survive at most any 1 region failure. |
+-----------------------------------------------------------------------------------------------+
Now, connect to the cluster and create a database for use in the application.
docker exec -it yugabytedb-node1 ysqlsh -U yugabyte -p 5433 -h yugabytedb-node1 -c "CREATE DATABASE iot_data;"
With a database up and running, you can begin to develop a sample application using Drizzle.
Drizzle and Drizzle Kit
The Drizzle ecosystem has already rapidly expanded to meet developer demands. Components include Drizzle, the core ORM for querying relational databases, and Drizzle Kit, a command-line tool for managing database migrations.
Here’s how I initialized a new project and installed the dependencies.
mkdir drizzle_iot && cd drizzle_iot
npm init -y
npm install drizzle-orm pg dotenv
npm install --save-dev drizzle-kit tsx @types/pg
This will install the Drizzle ORM and the associated node-postgres driver. If you want to include load balancing and topology awareness in the application, you can use the YugabyteDB node-postgres smart driver as a drop-in replacement. The dotenv package is installed for loading environment variables using a .env file.
Drizzle Kit is installed as a development dependency along with packages for running TypeScript files in Node.js.
Now, set up a connection to YugabyteDB to generate a database schema with Drizzle. The database connection string is defined in a .env file.
// .env
DATABASE_URL="postgresql://yugabyte:yugabyte@localhost:5433/iot_data"
This string can be used to instantiate a connection pool, which is passed to Drizzle.
// connection.ts
import "dotenv/config";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
export const db = drizzle(pool);
A Drizzle configuration file is used to define the locations of schema files and generated schema migration files, SQL dialects, database credentials, database extensions, and more.
// drizzle.config.ts
import "dotenv/config";
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/db/schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
Let’s define the schema.ts file referenced in this configuration file.
// schema.ts
import {
bigint,
pgTable,
bigserial,
text,
timestamp,
jsonb,
} from "drizzle-orm/pg-core";
// Table for storing sensor metadata
export const sensors = pgTable("sensors", {
id: bigserial({mode: "number"}).primaryKey(),
name: text("name").notNull(),
location: text("location"),
created_at: timestamp("created_at").defaultNow(),
});
// Table for storing sensor readings
export const sensorData = pgTable("sensor_data", {
id: bigserial({mode: "number"}).primaryKey(),
sensor_id: bigint({mode: "number"})
.notNull()
.references(() => sensors.id),
data: jsonb("data").notNull(), // JSONB column to store sensor readings
recorded_at: timestamp("recorded_at").defaultNow(),
});
Drizzle’s type definitions come in the form of parameterized functions. I have imported all of the types required to generate a schema with 2 tables, sensors and sensor_data.
The sensor_data table stores a foreign key to a particular sensor, with data stored as JSONB. This data type is particularly useful for storing sensor data, as each sensor will report different metrics. This eliminates the need to create a new structured table with the fields for each sensor.
Now, I generate migration files for this schema and run it against the database to create tables using Drizzle Kit.
npx drizzle-kit generate
No config path provided, using default 'drizzle.config.ts'
Reading config file '/Users/bhoyer/Projects/drizzle_iot/drizzle.config.ts'
2 tables
sensor_data 4 columns 0 indexes 1 fks
sensors 4 columns 0 indexes 0 fks
[✓] Your SQL migration file ➜ drizzle/0000_melted_mercury.sql 🚀
This generates a file with raw DDL statements to execute.
// drizzle/0000_melted_mercury.sql
CREATE TABLE IF NOT EXISTS "sensor_data" (
"id" bigserial PRIMARY KEY NOT NULL,
"sensor_id" bigint NOT NULL,
"data" jsonb NOT NULL,
"recorded_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "sensors" (
"id" bigserial PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"location" text,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "sensor_data" ADD CONSTRAINT "sensor_data_sensor_id_sensors_id_fk" FOREIGN KEY ("sensor_id") REFERENCES "public"."sensors"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
npx drizzle-kit migrate
Reading config file '/Users/bhoyer/Projects/drizzle_iot/drizzle.config.ts'
Using 'pg' driver for database querying
[✓] migrations applied successfully
By connecting to YugabyteDB using the ysqlsh CLI, I can verify that the migration succeeded as reported.
docker exec -it yugabytedb-node1 ysqlsh -U yugabyte -p 5433 -h yugabytedb-node1 -d iot_data
iot_data=# \d
List of relations
Schema | Name | Type | Owner
--------+--------------------+----------+----------
public | sensor_data | table | yugabyte
public | sensor_data_id_seq | sequence | yugabyte
public | sensors | table | yugabyte
public | sensors_id_seq | sequence | yugabyte
(4 rows)
Simulating Sensor Data
Let’s use our database to simulate sensor readings from devices. The beauty of Drizzle is that the very same functions used to define the schema can be used in our application code. This simultaneously ensures type safety and enhances the developer experience.
I start by creating three sensors.
// seedSensors.js
import { db } from "./connection";
import { sensors } from "./schema";
async function seedSensors() {
const sensorEntries = [
{ name: "Temperature Sensor", location: "Living Room" },
{ name: "Humidity Sensor", location: "Office" },
{ name: "Pressure Sensor", location: "Basement" },
];
for (const sensor of sensorEntries) {
await db.insert(sensors).values(sensor);
}
console.log("Sensors have been seeded!");
process.exit(0);
}
seedSensors().catch((err) => {
console.error("Error seeding sensors:", err);
process.exit(1);
});
Here, I can use the Drizzle db object to write to the database using the sensors object. Each write to the database takes an object with the name and location columns, which are the only columns requiring user-supplied data in the sensors schema.
This script can be executed using tsx.
npx tsx seedSensors.ts
With three sensors seeded in the database, let’s create a script for writing simulation data to the sensor_data table.
// simulateSensorData.ts
import { db } from "./connection";
import { sensorData, sensors } from "./schema";
// Generate random data based on sensor type
const generateSensorData = (sensorName: string) => {
switch (sensorName) {
case "Temperature Sensor":
return { temperature: (20 + Math.random() * 15).toFixed(1) }; // 20-35°C
case "Humidity Sensor":
return { humidity: (30 + Math.random() * 40).toFixed(1) }; // 30-70%
case "Pressure Sensor":
return { pressure: (950 + Math.random() * 50).toFixed(1) }; // 950-1000 hPa
default:
return { genericReading: Math.random().toFixed(2) }; // Default generic data
}
};
// Fetch all sensors
async function getSensors() {
return await db.select().from(sensors);
}
// Insert sensor readings into the database
async function insertSensorData(sensorId: number, sensorName: string) {
const data = generateSensorData(sensorName);
await db.insert(sensorData).values({
sensor_id: sensorId,
data,
});
console.log(`Inserted data for ${sensorName} (${sensorId}):`, data);
}
// Continuously generate and insert data
async function simulateSensorData() {
const allSensors = await getSensors();
if (!allSensors.length) {
console.error("No sensors found. Seed the database first.");
process.exit(1);
}
setInterval(async () => {
for (const sensor of allSensors) {
await insertSensorData(sensor.id, sensor.name);
}
}, 1000); // Insert data every second
}
simulateSensorData().catch((err) => {
console.error("Error during simulation:", err);
process.exit(1);
});
I first fetch all sensors from the database, before inserting representative sensor data every second.
By setting the foreign key sensor_id and JSONB data, I can associate data to a particular sensor. This simple example shows how Drizzle shares TypeScript objects across the project, aiding schema creation and application building.
We can run this script to see the records being inserted into the sensor_data table.
npx tsx src/db/simulateSensorData.ts
Inserted data for Temperature Sensor (1): { temperature: '32.2' }
Inserted data for Humidity Sensor (2): { humidity: '62.1' }
Inserted data for Pressure Sensor (3): { pressure: '976.4' }
Inserted data for Temperature Sensor (1): { temperature: '33.5' }
Inserted data for Humidity Sensor (2): { humidity: '45.6' }
Inserted data for Pressure Sensor (3): { pressure: '967.1' }
Inserted data for Temperature Sensor (1): { temperature: '34.1' }
Inserted data for Humidity Sensor (2): { humidity: '54.9' }
Inserted data for Pressure Sensor (3): { pressure: '969.7' }
Inserted data for Temperature Sensor (1): { temperature: '30.4' }
Why Drizzle?
The Node.js ecosystem has seen countless ORMs over the years, with major players like Sequelize, TypeORM, and Prisma being the most popular options.
However, all of these frameworks have tradeoffs. Some developers prefer lightweight query builders like Kysely and Knex, due to their performance and simplicity compared to the previously mentioned frameworks.
This is where Drizzle shines, bringing a lightweight and flexible design to the ORM landscape, without sacrificing the features enjoyed by users of higher-level systems with more abstractions.
Drizzle provides robust support for writing raw queries, relational queries, and select statements. Multiple ways to interface with the database allow developers with varied knowledge of SQL to work with the database.
Conclusion
Drizzle ORM is a great option if you want a next-gen solution to pair with your next-gen YugabyteDB database. The addition of Drizzle Kit for managing migrations and Drizzle Studio for visualizing data, allows the team to continue to meet the evolving needs of developers.
Want to learn more about developing apps with YugabyteDB? Check out one of these recent blogs:
Introduction
Choosing an ORM (Object-Relational Mapping) for your application is often a balance between performance, flexibility, and developer experience.
Drizzle ORMhas become a popular option for developers using relational databases to back JavaScript and TypeScript applications. The lightweight nature of this TypeScript-first framework optimizes performance and productivity without sacrificing type safety. Drizzle provides best-in-class feature completeness through dialect-specific packages. This allows developers to work with PostgreSQL and PostgreSQL-compatible databases like YugabyteDB.
In this blog, I’ll demonstrate how you can use Drizzle ORM to develop an IoT application with YugabyteDB. Structured and semi-structured (JSONB) data can be stored in the same database, making YugabyteDB the perfect choice for supporting heterogenous or evolving datasets.
Getting Started
Before setting up a TypeScript project with Drizzle, I’ll create a 3-node YugabyteDB cluster in Docker.
docker run -d --name yugabytedb-node1 --net test_net \ -p 15433:15433 -p 5433:5433 \ -v ~/yugabyte-volume/node1:/home/yugabyte/yb_data --restart unless-stopped \ yugabytedb/yugabyte:latest \ bin/yugabyted start --base_dir=/home/yugabyte/yb_data --background=false \ --cloud_location=gcp.us-east1.us-east1-a \ --fault_tolerance=region while ! docker exec -it yugabytedb-node1 postgres/bin/pg_isready -U yugabyte -h yugabytedb-node1; do sleep 1; done docker run -d --name yugabytedb-node2 --net test_net \ -p 15434:15433 -p 5434:5433 \ -v ~/yugabyte-volume/node2:/home/yugabyte/yb_data --restart unless-stopped \ yugabytedb/yugabyte:latest \ bin/yugabyted start --join=yugabytedb-node1 --base_dir=/home/yugabyte/yb_data --background=false \ --cloud_location=gcp.us-central1.us-central1-a \ --fault_tolerance=region docker run -d --name yugabytedb-node3 --net test_net \ -p 15435:15433 -p 5435:5433 \ -v ~/yugabyte-volume/node3:/home/yugabyte/yb_data --restart unless-stopped \ yugabytedb/yugabyte:latest \ bin/yugabyted start --join=yugabytedb-node1 --base_dir=/home/yugabyte/yb_data --background=false \ --cloud_location=gcp.us-west2.us-west2-a \ --fault_tolerance=region
This cluster can be configured to be fault-tolerant across regions. This means the data is replicated across regions allowing the cluster to withstand a full regional outage.
Here’s how to use the yugabyted command-line utility to configure the data placement for the cluster.
docker exec -it yugabytedb-node1 \ bin/yugabyted configure data_placement --fault_tolerance=region --base_dir=/home/yugabyte/yb_data +-----------------------------------------------------------------------------------------------+ | yugabyted | +-----------------------------------------------------------------------------------------------+ | Status : Configuration successful. Primary data placement is geo-redundant. | | Fault Tolerance : Primary Cluster can survive at most any 1 region failure. | +-----------------------------------------------------------------------------------------------+
Now, connect to the cluster and create a database for use in the application.
docker exec -it yugabytedb-node1 ysqlsh -U yugabyte -p 5433 -h yugabytedb-node1 -c "CREATE DATABASE iot_data;"
With a database up and running, you can begin to develop a sample application using Drizzle.
Drizzle and Drizzle Kit
The Drizzle ecosystem has already rapidly expanded to meet developer demands. Components include Drizzle, the core ORM for querying relational databases, and Drizzle Kit, a command-line tool for managing database migrations.
Here’s how I initialized a new project and installed the dependencies.
mkdir drizzle_iot && cd drizzle_iot npm init -y npm install drizzle-orm pg dotenv npm install --save-dev drizzle-kit tsx @types/pg
This will install the Drizzle ORM and the associated node-postgres driver. If you want to include load balancing and topology awareness in the application, you can use the YugabyteDB node-postgres smart driver as a drop-in replacement. The dotenv package is installed for loading environment variables using a .env file.
Drizzle Kit is installed as a development dependency along with packages for running TypeScript files in Node.js.
Now, set up a connection to YugabyteDB to generate a database schema with Drizzle. The database connection string is defined in a .env file.
// .env DATABASE_URL="postgresql://yugabyte:yugabyte@localhost:5433/iot_data"
This string can be used to instantiate a connection pool, which is passed to Drizzle.
// connection.ts import "dotenv/config"; import { drizzle } from "drizzle-orm/node-postgres"; import { Pool } from "pg"; const pool = new Pool({ connectionString: process.env.DATABASE_URL, }); export const db = drizzle(pool);
A Drizzle configuration file is used to define the locations of schema files and generated schema migration files, SQL dialects, database credentials, database extensions, and more.
// drizzle.config.ts import "dotenv/config"; import { defineConfig } from "drizzle-kit"; export default defineConfig({ schema: "./src/db/schema.ts", out: "./drizzle", dialect: "postgresql", dbCredentials: { url: process.env.DATABASE_URL!, }, });
Let’s define the schema.ts file referenced in this configuration file.
// schema.ts import { bigint, pgTable, bigserial, text, timestamp, jsonb, } from "drizzle-orm/pg-core"; // Table for storing sensor metadata export const sensors = pgTable("sensors", { id: bigserial({mode: "number"}).primaryKey(), name: text("name").notNull(), location: text("location"), created_at: timestamp("created_at").defaultNow(), }); // Table for storing sensor readings export const sensorData = pgTable("sensor_data", { id: bigserial({mode: "number"}).primaryKey(), sensor_id: bigint({mode: "number"}) .notNull() .references(() => sensors.id), data: jsonb("data").notNull(), // JSONB column to store sensor readings recorded_at: timestamp("recorded_at").defaultNow(), });
Drizzle’s type definitions come in the form of parameterized functions. I have imported all of the types required to generate a schema with 2 tables, sensors and sensor_data.
The sensor_data table stores a foreign key to a particular sensor, with data stored as JSONB. This data type is particularly useful for storing sensor data, as each sensor will report different metrics. This eliminates the need to create a new structured table with the fields for each sensor.
Now, I generate migration files for this schema and run it against the database to create tables using Drizzle Kit.
npx drizzle-kit generate No config path provided, using default 'drizzle.config.ts' Reading config file '/Users/bhoyer/Projects/drizzle_iot/drizzle.config.ts' 2 tables sensor_data 4 columns 0 indexes 1 fks sensors 4 columns 0 indexes 0 fks [✓] Your SQL migration file ➜ drizzle/0000_melted_mercury.sql 🚀
This generates a file with raw DDL statements to execute.
// drizzle/0000_melted_mercury.sql CREATE TABLE IF NOT EXISTS "sensor_data" ( "id" bigserial PRIMARY KEY NOT NULL, "sensor_id" bigint NOT NULL, "data" jsonb NOT NULL, "recorded_at" timestamp DEFAULT now() ); --> statement-breakpoint CREATE TABLE IF NOT EXISTS "sensors" ( "id" bigserial PRIMARY KEY NOT NULL, "name" text NOT NULL, "location" text, "created_at" timestamp DEFAULT now() ); --> statement-breakpoint DO $$ BEGIN ALTER TABLE "sensor_data" ADD CONSTRAINT "sensor_data_sensor_id_sensors_id_fk" FOREIGN KEY ("sensor_id") REFERENCES "public"."sensors"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; npx drizzle-kit migrate Reading config file '/Users/bhoyer/Projects/drizzle_iot/drizzle.config.ts' Using 'pg' driver for database querying [✓] migrations applied successfully
By connecting to YugabyteDB using the ysqlsh CLI, I can verify that the migration succeeded as reported.
docker exec -it yugabytedb-node1 ysqlsh -U yugabyte -p 5433 -h yugabytedb-node1 -d iot_data iot_data=# \d List of relations Schema | Name | Type | Owner --------+--------------------+----------+---------- public | sensor_data | table | yugabyte public | sensor_data_id_seq | sequence | yugabyte public | sensors | table | yugabyte public | sensors_id_seq | sequence | yugabyte (4 rows)
Simulating Sensor Data
Let’s use our database to simulate sensor readings from devices. The beauty of Drizzle is that the very same functions used to define the schema can be used in our application code. This simultaneously ensures type safety and enhances the developer experience.
I start by creating three sensors.
// seedSensors.js import { db } from "./connection"; import { sensors } from "./schema"; async function seedSensors() { const sensorEntries = [ { name: "Temperature Sensor", location: "Living Room" }, { name: "Humidity Sensor", location: "Office" }, { name: "Pressure Sensor", location: "Basement" }, ]; for (const sensor of sensorEntries) { await db.insert(sensors).values(sensor); } console.log("Sensors have been seeded!"); process.exit(0); } seedSensors().catch((err) => { console.error("Error seeding sensors:", err); process.exit(1); });
Here, I can use the Drizzle db object to write to the database using the sensors object. Each write to the database takes an object with the name and location columns, which are the only columns requiring user-supplied data in the sensors schema.
This script can be executed using tsx.
npx tsx seedSensors.ts
With three sensors seeded in the database, let’s create a script for writing simulation data to the sensor_data table.
// simulateSensorData.ts import { db } from "./connection"; import { sensorData, sensors } from "./schema"; // Generate random data based on sensor type const generateSensorData = (sensorName: string) => { switch (sensorName) { case "Temperature Sensor": return { temperature: (20 + Math.random() * 15).toFixed(1) }; // 20-35°C case "Humidity Sensor": return { humidity: (30 + Math.random() * 40).toFixed(1) }; // 30-70% case "Pressure Sensor": return { pressure: (950 + Math.random() * 50).toFixed(1) }; // 950-1000 hPa default: return { genericReading: Math.random().toFixed(2) }; // Default generic data } }; // Fetch all sensors async function getSensors() { return await db.select().from(sensors); } // Insert sensor readings into the database async function insertSensorData(sensorId: number, sensorName: string) { const data = generateSensorData(sensorName); await db.insert(sensorData).values({ sensor_id: sensorId, data, }); console.log(`Inserted data for ${sensorName} (${sensorId}):`, data); } // Continuously generate and insert data async function simulateSensorData() { const allSensors = await getSensors(); if (!allSensors.length) { console.error("No sensors found. Seed the database first."); process.exit(1); } setInterval(async () => { for (const sensor of allSensors) { await insertSensorData(sensor.id, sensor.name); } }, 1000); // Insert data every second } simulateSensorData().catch((err) => { console.error("Error during simulation:", err); process.exit(1); });
I first fetch all sensors from the database, before inserting representative sensor data every second.
By setting the foreign key sensor_id and JSONB data, I can associate data to a particular sensor. This simple example shows how Drizzle shares TypeScript objects across the project, aiding schema creation and application building.
We can run this script to see the records being inserted into the sensor_data table.
npx tsx src/db/simulateSensorData.ts Inserted data for Temperature Sensor (1): { temperature: '32.2' } Inserted data for Humidity Sensor (2): { humidity: '62.1' } Inserted data for Pressure Sensor (3): { pressure: '976.4' } Inserted data for Temperature Sensor (1): { temperature: '33.5' } Inserted data for Humidity Sensor (2): { humidity: '45.6' } Inserted data for Pressure Sensor (3): { pressure: '967.1' } Inserted data for Temperature Sensor (1): { temperature: '34.1' } Inserted data for Humidity Sensor (2): { humidity: '54.9' } Inserted data for Pressure Sensor (3): { pressure: '969.7' } Inserted data for Temperature Sensor (1): { temperature: '30.4' }
Why Drizzle?
The Node.js ecosystem has seen countless ORMs over the years, with major players like Sequelize, TypeORM, and Prisma being the most popular options.
However, all of these frameworks have tradeoffs. Some developers prefer lightweight query builders like Kysely and Knex, due to their performance and simplicity compared to the previously mentioned frameworks.
This is where Drizzle shines, bringing a lightweight and flexible design to the ORM landscape, without sacrificing the features enjoyed by users of higher-level systems with more abstractions.
Drizzle provides robust support for writing raw queries, relational queries, and select statements. Multiple ways to interface with the database allow developers with varied knowledge of SQL to work with the database.
Conclusion
Drizzle ORM is a great option if you want a next-gen solution to pair with your next-gen YugabyteDB database. The addition of Drizzle Kit for managing migrations and Drizzle Studio for visualizing data, allows the team to continue to meet the evolving needs of developers.
Want to learn more about developing apps with YugabyteDB? Check out one of these recent blogs: