Create Records
Creating records is a fundamental operation when working with any data-driven application. RushDB provides multiple ways to create records, from direct API calls to Model-based abstractions.
This guide covers different approaches to creating records, from the most basic to more advanced patterns.
Overview
The create record methods in the SDK enable you to:
- Create a single record with properties and a label
- Create multiple records in one operation
- Upsert records (create or update based on matching criteria)
- Control data type inference and other formatting options
- Create records with precise type control
- Create records within transactions for data consistency
- Create records using Model abstractions for type safety
Creating Single Records
There are multiple ways to create records in RushDB. Let's start with the most basic approach using the direct API methods.
Using RushDB's create() Method
The most direct way to create a record is using the API client's records.create method:
const newAuthor = await db.records.create({
label: 'AUTHOR',
data: {
name: 'John Doe',
email: 'john.doe@example.com'
},
options: {
suggestTypes: true
}
});
console.log(newAuthor);
/*
{
__id: 'generated_id',
__label: 'AUTHOR',
name: 'John Doe',
email: 'john.doe@example.com'
}
*/
Parameters
label: The label/type for the recorddata: The data for the record as a flat objectoptions(optional): Configuration options for record creation:suggestTypes(boolean, default:true): Automatically infers data types for properties. Set tofalseto disable type inference and store all values as stringscastNumberArraysToVectors(boolean, default:false): When true, converts numeric arrays to vector typeconvertNumericValuesToNumbers(boolean, default:false): When true, converts string numbers to number type
transaction(optional): A transaction object or string to include the operation within a transaction
By default, suggestTypes is true for all write operations. RushDB automatically infers data types from your values. To disable this and store all properties as strings, explicitly set suggestTypes: false.
Returns
- A promise that resolves to a
DBRecordInstancecontaining the created record
Creating Records in Transactions
const transaction = await db.tx.begin();
try {
const newAuthor = await db.records.create({
label: 'AUTHOR',
data: {
name: 'Jane Smith',
email: 'jane.smith@example.com'
}
}, transaction);
// Perform other operations...
await transaction.commit();
console.log(newAuthor);
} catch (error) {
await transaction.rollback();
throw error;
}
Property-Based Approach for Precise Type Control
When you need precise control over property types, you can use the property-based approach by passing an array of PropertyDraft objects instead of a flat data object:
const newAuthor = await db.records.create({
label: 'AUTHOR',
data: [
{
name: 'name',
type: 'string',
value: 'John Doe'
},
{
name: 'age',
type: 'number',
value: 42
},
{
name: 'isActive',
type: 'boolean',
value: true
},
{
name: 'tags',
type: 'string',
value: 'fiction,sci-fi,bestseller',
valueSeparator: ','
},
{
name: 'scores',
type: 'number',
value: '85,90,95',
valueSeparator: ','
},
{
name: 'joinDate',
type: 'datetime',
value: '2025-04-23T10:30:00Z'
}
]
});
console.log(newAuthor);
/*
{
__id: 'generated_id',
__label: 'AUTHOR',
__proptypes: {
name: 'string',
age: 'number',
isActive: 'boolean',
tags: 'string',
scores: 'number',
joinDate: 'datetime'
},
name: 'John Doe',
age: 42,
isActive: true,
tags: ['fiction', 'sci-fi', 'bestseller'],
scores: [85, 90, 95],
joinDate: '2025-04-23T10:30:00Z'
}
*/
Property Draft Object Properties
Each property draft object supports the following properties:
| Property | Type | Description |
|---|---|---|
name | string | The property name |
type | string | The data type ('string', 'number', 'boolean', 'datetime', etc.) |
value | any | The property value |
valueSeparator | string (optional) | Separator to split string values into arrays |
Creating Multiple Records
When you need to create multiple flat records (CSV-like rows) in a single operation, use the records.createMany method. For nested or complex JSON, use records.importJson.
Using RushDB's createMany() Method (flat rows only)
const authors = await db.records.createMany({
label: 'AUTHOR',
data: [
{ name: 'Alice Johnson', email: 'alice.johnson@example.com' },
{ name: 'Bob Brown', email: 'bob.brown@example.com' }
],
options: {
suggestTypes: true
}
});
console.log(authors);
/*
{
data: [
{
__id: 'generated_id_1',
__label: 'AUTHOR',
name: 'Alice Johnson',
email: 'alice.johnson@example.com'
},
{
__id: 'generated_id_2',
__label: 'AUTHOR',
name: 'Bob Brown',
email: 'bob.brown@example.com'
}
],
total: 2
}
*/
Parameters
-
label: The label/type for all records -
data: An object or array of objects, each a flat record (no nested objects/arrays) -
options(optional): Configuration options for record creation:suggestTypes(boolean, default:true): Automatically infers data types for properties. Set tofalseto disable type inferencecastNumberArraysToVectors(boolean, default:false): When true, converts numeric arrays to vector typeconvertNumericValuesToNumbers(boolean, default:false): When true, converts string numbers to number typecapitalizeLabels(bool): When true, converts all labels to uppercaserelationshipType(str): Default relationship type between nodesreturnResult(bool, default:false): When true, returns imported records in response- Throws if any record contains nested objects/arrays. Use
records.importJsonfor that.
Using RushDB's
importJson()Method (nested JSON)Use
importJsonfor nested objects, arrays of nested objects, or hash-map like payloads.Signature:
db.records.importJson({ data, label?: string, options?: ImportOptions }, tx?)Behavior:
- If
labelis provided, it's used for the import. - If
labelis omitted, the input must be an object with a single top-level key whose name becomes the label, e.g.{ ITEM: [ {...}, {...} ] }. - If
labelis omitted and the object has multiple top-level keys (e.g.{ some: 'key', data: 1, nested: { level: 2 } }), an error is thrown.
Multiple top-level keys:
- Without
label: not allowed — importJson requires a single top-level key to infer the label and will throw. - With
label: allowed — the providedlabelbecomes the root label; the multiple keys are treated as nested structure under that root. - If you want each top-level key to become its own label root, call
importJsonseparately per key or pass single-key objects per call.
Examples:
- OK (label inferred):
{ "ITEM": [ { /*...*/ }, { /*...*/ } ] } - OK (label inferred with object):
{ "ITEM": { /*...*/ } } - OK with explicit label (multiple top-level keys):
Call as:
{ "ITEM": { /*...*/ }, "PRODUCT": { /*...*/ } }db.records.importJson({ label: 'INVENTORY', data: { ITEM: {...}, PRODUCT: {...} } }) - Will throw without label (multiple top-level keys):
{ "ITEM": { /*...*/ }, "PRODUCT": { /*...*/ } } - Will throw without label (mixed keys):
{ "ITEM": { /*...*/ }, "notNestedProp": "12" }
-
transaction(optional): A transaction object or string to include the operation within a transaction
Returns
- A promise that resolves to a
DBRecordsArrayInstancecontaining the created records
Creating Multiple Records in Transactions
const transaction = await db.tx.begin();
try {
const authors = await db.records.createMany({
label: 'AUTHOR',
data: [
{ name: 'Charlie Green', email: 'charlie.green@example.com' },
{ name: 'David Blue', email: 'david.blue@example.com' }
]
}, transaction);
// Perform other operations...
await transaction.commit();
console.log(authors);
} catch (error) {
await transaction.rollback();
throw error;
}
Upserting Records
The upsert method provides a powerful way to create or update records in a single operation. It attempts to find an existing record based on specified properties and either creates a new one or updates the existing record according to your chosen strategy.
Using RushDB's upsert() Method
const product = await db.records.upsert({
label: 'Product',
data: {
sku: 'SKU-001',
name: 'Laptop Pro',
price: 1299.99,
category: 'Electronics'
},
options: {
mergeBy: ['sku'],
mergeStrategy: 'append',
suggestTypes: true
}
});
console.log(product);
/*
{
__id: 'generated_id',
__label: 'Product',
sku: 'SKU-001',
name: 'Laptop Pro',
price: 1299.99,
category: 'Electronics'
}
*/
Parameters
label(optional): The label/type for the recorddata: Flat object or array of property drafts containing the record dataoptions(optional): Configuration options for the upsert operation:mergeBy(string[], optional): Property names to match on. If empty/undefined, matches on all incoming propertiesmergeStrategy('rewrite' | 'append', default: 'append'): Strategy for handling updatessuggestTypes(boolean, default:true): Automatically infers data types for properties. Set tofalseto disable type inferencecastNumberArraysToVectors(boolean, default:false): Converts numeric arrays to vector typeconvertNumericValuesToNumbers(boolean, default:false): Converts string numbers to number type
transaction(optional): A transaction object or string to include the operation within a transaction
By default, suggestTypes is true for all write operations including upsert. RushDB automatically infers data types from your values. To disable this and store all properties as strings, explicitly set suggestTypes: false.
Returns
- A promise that resolves to a
DBRecordInstancecontaining the created or updated record
Merge Strategies
Append Strategy
The append strategy (default) updates or adds properties while preserving existing ones:
// Initial create
const product = await db.records.upsert({
label: 'Product',
data: { sku: 'SKU-001', name: 'Widget', price: 10, category: 'Tools' },
options: { mergeBy: ['sku'], mergeStrategy: 'append', suggestTypes: true }
});
// Update price and add stock - name and category are preserved
const updated = await db.records.upsert({
label: 'Product',
data: { sku: 'SKU-001', price: 15, stock: 100 },
options: { mergeBy: ['sku'], mergeStrategy: 'append', suggestTypes: true }
});
console.log(updated.data);
/*
{
sku: 'SKU-001',
name: 'Widget', // Preserved
category: 'Tools', // Preserved
price: 15, // Updated
stock: 100 // Added
}
*/
Rewrite Strategy
The rewrite strategy replaces all properties with the incoming data:
// Rewrite - removes unspecified fields
const rewritten = await db.records.upsert({
label: 'Product',
data: { sku: 'SKU-001', name: 'New Widget', price: 20 },
options: { mergeBy: ['sku'], mergeStrategy: 'rewrite', suggestTypes: true }
});
console.log(rewritten.data);
/*
{
sku: 'SKU-001',
name: 'New Widget',
price: 20
// category and stock are removed
}
*/
Common Use Cases
Idempotent Data Imports
// Can be safely run multiple times without creating duplicates
const user = await db.records.upsert({
label: 'User',
data: {
email: 'john@example.com',
name: 'John Doe',
lastLogin: new Date().toISOString()
},
options: {
mergeBy: ['email'],
mergeStrategy: 'append',
suggestTypes: true
}
});
Multi-Tenant Applications
// Match on both tenant and entity identifiers
const setting = await db.records.upsert({
label: 'Setting',
data: {
tenantId: 'tenant-123',
userId: 'user-456',
theme: 'dark',
notifications: true
},
options: {
mergeBy: ['tenantId', 'userId'],
mergeStrategy: 'append',
suggestTypes: true
}
});
Configuration Management
// Update configuration by key
const config = await db.records.upsert({
label: 'Config',
data: {
key: 'api_timeout',
value: 30000,
updatedAt: new Date().toISOString()
},
options: {
mergeBy: ['key'],
mergeStrategy: 'append',
suggestTypes: true
}
});
Inventory Updates
// Update stock while preserving product details
const inventory = await db.records.upsert({
label: 'Product',
data: {
productCode: 'PROD-789',
stock: 50,
lastRestocked: new Date().toISOString()
},
options: {
mergeBy: ['productCode'],
mergeStrategy: 'append',
suggestTypes: true
}
});
Matching Behavior
With Specific MergeBy Fields
When mergeBy contains specific field names, only those fields are used for matching:
// Matches only on 'email'
const user = await db.records.upsert({
label: 'User',
data: { email: 'user@example.com', name: 'John', age: 30 },
options: { mergeBy: ['email'], mergeStrategy: 'append' }
});
Without MergeBy (All Properties Match)
When mergeBy is empty or undefined, matching is performed on all incoming properties:
// Matches only if ALL properties (email, name, age) match exactly
const user = await db.records.upsert({
label: 'User',
data: { email: 'user@example.com', name: 'John', age: 30 },
options: { mergeStrategy: 'append' }
});
// This would create a new record (age doesn't match)
const different = await db.records.upsert({
label: 'User',
data: { email: 'user@example.com', name: 'John', age: 31 },
options: { mergeStrategy: 'append' }
});
Using with Transactions
const transaction = await db.tx.begin();
try {
const product = await db.records.upsert({
label: 'Product',
data: { sku: 'SKU-001', name: 'Widget', price: 10 },
options: { mergeBy: ['sku'], mergeStrategy: 'append' }
}, transaction);
const inventory = await db.records.upsert({
label: 'Inventory',
data: { productSku: 'SKU-001', quantity: 100, warehouse: 'A' },
options: { mergeBy: ['productSku', 'warehouse'], mergeStrategy: 'append' }
}, transaction);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
Property-Based Upsert
For precise type control, you can use property drafts:
const record = await db.records.upsert({
label: 'Product',
data: [
{ name: 'sku', type: 'string', value: 'SKU-001' },
{ name: 'price', type: 'number', value: 99.99 },
{ name: 'tags', type: 'string', value: 'electronics,sale', valueSeparator: ',' },
{ name: 'inStock', type: 'boolean', value: true }
],
options: {
mergeBy: ['sku'],
mergeStrategy: 'append'
}
});
Best Practices for Upsert
-
Choose Appropriate MergeBy Fields
- Use fields that uniquely identify your records (like
email,sku,userId) - Consider multi-field matching for multi-tenant scenarios
- Use fields that uniquely identify your records (like
-
Select the Right Strategy
- Use
appendwhen you want to preserve existing data and only update specific fields - Use
rewritewhen you need a complete replacement of the record
- Use
-
Use with Transactions for Related Updates
- Combine multiple upserts in a transaction to ensure atomicity
- Roll back if any operation fails
-
Handle Edge Cases
- Be aware that empty
mergeBymeans matching on all properties - Consider performance implications when matching on many fields
- Be aware that empty
-
Idempotent Operations
- Upsert is ideal for data synchronization and import operations
- Safely re-run operations without creating duplicates
Creating Records with Models
The recommended approach for structured applications is to use RushDB's Models. Models provide type safety, validation, and a more intuitive API for working with records.
We'll use the following model definitions for these examples:
const AuthorRepo = new Model('author', {
name: { type: 'string' },
email: { type: 'string', unique: true }
});
Using Model's create Method
The create method on a model creates a single record.
Signature
create(
record: InferSchemaTypesWrite<S>,
transaction?: Transaction | string
): Promise<DBRecordInstance<S>>;
Parameters
record: An object that adheres to the schema defined for the modeltransaction(optional): A transaction object or string to include the operation within a transaction
Returns
- A promise that resolves to a
DBRecordInstancecontaining the created record
Example
const newAuthor = await AuthorRepo.create({
name: 'John Doe',
email: 'john.doe@example.com'
});
console.log(newAuthor);
/*
{
data: {
__id: 'generated_id',
__label: 'author',
name: 'John Doe',
email: 'john.doe@example.com'
}
}
*/
Using with Transactions
const transaction = await db.tx.begin();
try {
const newAuthor = await AuthorRepo.create({
name: 'Jane Smith',
email: 'jane.smith@example.com'
}, transaction);
// Perform other operations...
await transaction.commit();
console.log(newAuthor);
} catch (error) {
await transaction.rollback();
throw error;
}
Using Model's createMany Method
The createMany method on a model creates multiple records in a single operation.
Signature
createMany(
records: Array<InferSchemaTypesWrite<S>>,
transaction?: Transaction | string
): Promise<DBRecordsArrayInstance<S>>;
Parameters
records: An array of objects, each adhering to the schema defined for the modeltransaction(optional): A transaction object or string to include the operation within a transaction
Returns
- A promise that resolves to a
DBRecordsArrayInstancecontaining the created records
Example
const authors = await AuthorRepo.createMany([
{ name: 'Alice Johnson', email: 'alice.johnson@example.com' },
{ name: 'Bob Brown', email: 'bob.brown@example.com' }
]);
console.log(authors);
/*
{
data: [
{
__id: 'generated_id_1',
__label: 'author',
name: 'Alice Johnson',
email: 'alice.johnson@example.com'
},
{
__id: 'generated_id_2',
__label: 'author',
name: 'Bob Brown',
email: 'bob.brown@example.com'
}
],
total: 2
}
*/
Using with Transactions
const transaction = await db.tx.begin();
try {
const authors = await AuthorRepo.createMany([
{ name: 'Charlie Green', email: 'charlie.green@example.com' },
{ name: 'David Blue', email: 'david.blue@example.com' }
], transaction);
// Perform other operations...
await transaction.commit();
console.log(authors);
} catch (error) {
await transaction.rollback();
throw error;
}
Best Practices for Creating Records
-
Use Models for Structured Applications
- Models provide type safety, validation, and better organization
- They enforce schema consistency across your application
-
Use Transactions for Related Operations
- When creating multiple records that are related, use transactions
- Transactions ensure data consistency and allow rollback if operations fail
-
Handle Uniqueness Constraints
- Models automatically check uniqueness before creating records
- Handle
UniquenessErrorexceptions appropriately
-
Leverage Batch Operations
- Use
createManyfor better performance when creating multiple records - It minimizes network requests and database overhead
- Use
-
Consider Default Values
- Define default values in your schema to reduce repetitive code
- Default values can be static or derived from functions (like timestamps)
-
Choose the Right Data Type Control Approach
- Use the flat object approach for most cases where automatic type inference is sufficient
- Use the property-based approach with
PropertyDraftobjects when you need precise control over types
Data Type Handling
RushDB supports the following property types:
string: Text valuesnumber: Numeric valuesboolean: True/false valuesnull: Null valuesdatetime: ISO8601 format strings (e.g., "2025-04-23T10:30:00Z")vector: Arrays of numbers (whencastNumberArraysToVectorsis true)
Automatic Type Inference
By default, suggestTypes is set to true for all write operations (create, createMany, upsert, importJson). This means RushDB automatically infers data types from your values:
- Numeric values become
numbertype true/falsebecomebooleantype- ISO8601 strings become
datetimetype nullbecomesnulltype- All other values become
stringtype
To disable automatic type inference and store all values as strings, you must explicitly set suggestTypes: false in your options.
Additional Type Conversions
When convertNumericValuesToNumbers is enabled, string values that represent numbers (e.g., '30') will be converted to their numeric equivalents (e.g., 30).
When castNumberArraysToVectors is enabled, numeric arrays will be stored as vector type instead of number arrays.
For more complex data import operations, refer to the Import Data documentation.
Conclusion
Creating records in RushDB can be done through direct API calls or through the Model abstraction. While direct API calls offer flexibility for dynamic or ad-hoc operations, using Models is recommended for most applications due to their type safety, validation capabilities, and more intuitive API.
For more advanced record operations, see the other guides in this section:
- Get Records - Retrieve records from the database
- Update Records - Modify existing records
- Delete Records - Remove records from the database
- Import Data - Import data in bulk