Table Creation Guide
Table Creation Guide This guide walks you through creating your first table in Enfyra, from basic setup to understanding what happens after creation. Prerequisites : Make sure you've completed Installation and are familiar with the Getting Started Guide . Accessing Table Creation
Table Creation Guide
This guide walks you through creating your first table in Enfyra, from basic setup to understanding what happens after creation.
Prerequisites: Make sure you've completed Installation and are familiar with the Getting Started Guide.
Accessing Table Creation
- Click on Collections in the sidebar
- Click on "+ Create New Table" button in the left panel
Important: Order of Creation
You must create tables in the right order for relations to work:
- Create target tables first - If you want to create relations (like "Post Category"), you must create the "Category" table before creating the "Post" table
- Add columns before constraints - Unique constraints and indexes need existing columns to reference
- Add columns/relations before constraints - Both unique constraints and indexes need existing fields to select from
Table Creation Form
You'll see the table creation form with these sections:
Basic Information
- Table Name - Enter a name for your table (e.g., "products", "posts")
- Description - Optional description of what this table stores
Columns Configuration
Columns - Add fields to your table (an "id" field is added by default as primary key with auto increment) - Click green "+ Add Column" button to add new fields - Configure column properties in the drawer:
Default ID Field
Every table automatically gets an id field with these properties:
- Type: int (integer)
- Primary Key: Yes
- Auto Increment: Yes (automatically increments for each new record)
- Nullable: No (required field)
- Updatable: No (system managed)
You can change the id field type to uuid if you prefer UUID identifiers, but int with auto-increment is the default and recommended for most use cases.
Column Properties
Basic Settings:
- name - Field name (required, cannot start with number or _)
- type - Data type (see Field Types below)
- isNullable - Toggle to allow empty/null values
- isUpdatable - Toggle to allow field updates after creation
- isGenerated - Auto-set for uuid type (system managed)
- isPublished - Visibility baseline (default: true). Set to false to block access by default — use field permission rules to grant access to specific roles/users
- isEncrypted - Encrypt string-like field values at rest in the database access layer. Scripts, REST, and repositories read and write plaintext; Enfyra encrypts on insert/update and decrypts after select. Encrypted fields cannot be used for filter or sort. isEncrypted does not make a field immutable; set isUpdatable=false separately only when the value must not change after creation
Display & UX:
- description - Field documentation with rich text editor (displays as help text under field labels in forms)
- placeholder - Input placeholder text for forms
- options - For enum/array-select types: define available choices
Default Values:
- defaultValue - Default value for new records:
- For boolean: true/false selector
- For int/number: numeric input
- For date: date picker
- For enum: dropdown from options
- For array-select: multi-select from options
- For uuid: auto-generated (disabled input)
- For text types: text input
Field Types
| Type | Description | Use Case |
|---|---|---|
uuid |
Auto-generated UUID (sets isGenerated to true automatically) | Unique identifiers |
varchar |
Variable length text | Names, titles, short text |
text |
Long text content | Descriptions, articles |
int, bigint, number |
Numeric values. Note: When used for id field, automatically becomes auto-increment primary key |
Prices, quantities, IDs |
boolean |
True/false values | Flags, status indicators |
date, timestamp |
Date/time values | Created dates, deadlines |
enum |
Single selection from predefined options | Status, categories |
array-select |
Multiple selection from predefined options | Tags, multiple categories |
simple-json |
JSON data | Complex structured data |
richtext |
Rich text with formatting | Articles, descriptions with formatting |
code |
Code snippets with syntax highlighting | Code examples, scripts |
Advanced Configuration
Unique Constraints
- Only appears after you add columns - You need existing columns to select from
- Click green "+ Add" button to create a constraint group
- Select one or more fields that must be unique together
- Important: Creating a unique constraint automatically creates an index, so you don't need to add a separate index for the same fields
Index Configuration
- Only appears after you add columns and relations - You can index both table columns and relation fields
- Click green "+ Add" button to create an index group
- Select one or more fields to index together
- Tip: Don't create indexes on fields that already have unique constraints
Relations Setup
- Only shows existing tables - You must create target tables first before you can relate to them
- Displays existing relations with badges showing type, target table, and nullable status
- Click on existing relation to edit, or click green "+ Add Relation" button for new
- Configure relation properties in the drawer:
- type - Relationship type (one-to-one, one-to-many, many-to-one, many-to-many)
- propertyName - Name of the property in current table (required)
- inversePropertyName - Name of the reverse property in target table (optional, required for O2M)
- targetTable - Select the target table from available tables (only shows tables you've already created)
- isNullable - Toggle to allow null relations
- description - Relation documentation with rich text editor
- Relations list shows: property name, type badge, target table badge ( TargetTable), nullable badge if applicable
- Note: Once created, these relations will appear as fields with pencil icons in forms - see Relation Picker System for how to use them
Incoming relations (other tables pointing here): If another table already has a relation toward this table but you have not yet created the reverse property on this table, those links appear as dashed rows labeled incoming, with the source shown as SourceTable.propertyName. Use Create Inverse to open the dialog, choose a property name for the new relation on the current table, and save the table schema. After that, the relation behaves like any other row in the list; relations that are the inverse side of another definition show an inverse badge.
onDelete (cascade behavior)
When configuring relations that create foreign keys (one-to-one, one-to-many, many-to-one, many-to-many), Enfyra exposes an onDelete option that controls what happens when related records are deleted:
- CASCADE
- 1‑N / N‑1: Deleting the parent record will automatically delete all children that point to it (database-level cascade).
- 1‑1: Deleting a record on either side will delete the paired record as well (the system enforces this symmetrically at runtime).
-
N‑N: Cascade applies to the junction rows; deleting one side removes its links, but does not delete rows in the other table.
-
SET NULL
- Deleting the parent record will keep child records, but set their foreign key to
NULL. -
Useful when the child data can live independently after the parent is gone.
-
RESTRICT / NO ACTION
- Database will block deletion of a parent record if there are still related children.
- Use this when you want to force explicit cleanup (delete or re-assign children) before deleting the parent.
Current runtime behavior notes:
- Deleting records
- 1‑N / N‑1: Actual delete behavior is controlled by the database foreign key
onDeletesetting (CASCADE / SET NULL / RESTRICT). - 1‑1 with
onDelete = CASCADE: Enfyra ensures that deleting either side of the relation will also delete the other side, even when only one side has the physical FK. -
N‑N: Deleting a record removes its junction rows according to FK rules; data in the opposite table is not touched.
-
Updating relations through payloads
- N‑N: Updating a relation array (e.g.
tags) replaces rows in the junction table to match the new list of IDs/objects; target entities themselves are never deleted, only links change. - 1‑N: When you update a parent with a list of children, removed children are currently detached (their FK is set to
NULL) rather than deleted; theonDeleteoption primarily affects what happens when the parent row itself is deleted.
Practical cascade examples
- User ↔ Profile (1‑1, CASCADE)
- Relation:
user.profile(one‑to‑one),onDelete = CASCADE. -
Effect: Deleting a user deletes the attached profile; deleting the profile also deletes the user. Use this when neither record should exist without the other.
-
Post ↔ Comment (1‑N)
- Relation:
post.comments(one‑to‑many) tocomment.post. onDelete = CASCADE: Deleting a post deletes all its comments.onDelete = SET NULL: Deleting a post keeps comments but clears theirpostId, so you can re‑attach or inspect them later.-
onDelete = RESTRICT: You must delete or reassign comments before deleting the post. -
Post ↔ Tag (N‑N)
- Relation:
post.tags(many‑to‑many) through a junction table. - Removing a tag from
post.tagsonly removes the junction row; thetagrecord itself remains. - Deleting a
postortagwill clean up junction rows via FKonDelete, but will not touch the opposite table’s main records.
** IMPORTANT for MongoDB users: - When you update or delete a relation after creating it, all relation data will be dropped from your records - This includes both the relation field AND the inverse field - You will need to re-populate the data after changing relation metadata - Note**: MongoDB relations work differently than SQL relations. See Query Filtering for details on querying related data.
Saving Your Table
After configuring your table, click the green "+ Create New Table" button at the top right to save it.
What Happens After Creating a Table
Once saved, Enfyra automatically sets up several things for your new table:
1. API Endpoints Generated on Backend
- REST and GraphQL APIs are instantly available on your backend server (port 1105)
- 4 CRUD endpoints are automatically created:
GET /[your-table-name]- List all recordsPOST /[your-table-name]- Create new recordPATCH /[your-table-name]/:id- Update recordDELETE /[your-table-name]/:id- Delete record- Important: APIs exist on backend, frontend consumes them via HTTP requests
2. Route Automatically Created
- A new route
/[your-table-name]is automatically generated on the backend - This route handles the 4 CRUD operations above
- You can view this route: Go to Settings Routings in the sidebar to see all your table routes
3. Frontend Integration
- Your new table appears in the Data section of the sidebar
- Frontend makes HTTP requests to backend APIs to interact with data
- Click Data to see your table listed and start adding records
Next Steps
- Navigate to Data [Your Table Name] to start adding records - see Data Management for complete guide
- Remember: All data operations flow through backend APIs, frontend never touches database directly
- Use the Relation Picker System when working with relation fields
- Use the Filter System to search and filter your data
Best Practices
Table Design
- Plan relationships first - Create target tables before tables that reference them
- Use descriptive names - Clear table and column names improve maintainability
- Set appropriate constraints - Use unique constraints and indexes for data integrity and performance
Column Configuration
- Add descriptions - Help users understand field purposes
- Set sensible defaults - Reduce data entry effort
- Use appropriate types - Match field types to data requirements
- Protect secrets with
isEncrypted=true- Use encrypted, unpublished fields for API keys, tokens, passwords, private keys, and other stored secrets. Do not build manual pre-hook encryption for normal app data. If a secret should only be set once, also setisUpdatable=false
Performance Considerations
- Index frequently queried fields - Improve query performance
- Do not duplicate time indexes - Enfyra automatically creates single-field indexes for
createdAt,updatedAt, and scalardate,datetime, ortimestampcolumns. SQL indexes includeidas a stable tie-breaker; Mongo indexes include_id. - Use compound indexes for hot query shapes - Add explicit compound indexes when the query combines a time field with other filters, such as
status + createdAt,owner + updatedAt, orproject + lastMessageAt. - Avoid over-indexing - Too many indexes can slow down writes
- Consider data volume - Plan for growth in your table design
Common Patterns
User Management Table
Table: user_definition (built-in, but as example)
- id (uuid, generated)
- email (varchar, unique)
- name (varchar)
- isActive (boolean, default: true)
- createdAt (timestamp, default: now)
Product Catalog
Table: products
- id (uuid, generated)
- name (varchar)
- description (text)
- price (number)
- categoryId (relation to categories)
- isActive (boolean, default: true)
Blog System
Table: categories (create first)
- id (uuid, generated)
- name (varchar, unique)
- slug (varchar, unique)
Table: posts (create after categories)
- id (uuid, generated)
- title (varchar)
- content (richtext)
- categoryId (relation to categories)
- publishedAt (timestamp, nullable)
Troubleshooting
Common Issues
Cannot create relation: - Ensure target table exists first - Check that you're selecting the correct target table
Unique constraint errors: - Add columns before creating constraints - Ensure constraint fields are properly selected
Index creation fails: - Add columns and relations before creating indexes - Avoid indexing fields that already have unique constraints
Table creation fails: - Check table name doesn't conflict with existing tables - Ensure all required fields are properly configured - Verify relation configurations are valid
Related Documentation
- Data Management - Working with records in your tables
- Schema migration preview - Confirmed PATCH when removing columns/relations
- Relation Picker System - Using relation fields in forms
- Filter System - Searching and filtering data
- Form System - How forms are generated from tables
- Query Filtering - Advanced querying capabilities