Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nayalsaurav/Storx/llms.txt

Use this file to discover all available pages before exploring further.

Storx uses Drizzle ORM on top of Neon serverless PostgreSQL to store all file and folder metadata. ImageKit holds the actual file bytes; the database tracks ownership, hierarchy, and flags for every file and folder a user creates.

Creating a Neon Database

1

Sign up at neon.tech

Go to neon.tech and create a free account. Neon’s free tier includes a single project with up to 0.5 GB of storage — enough for development and small deployments.
2

Create a new project

From the Neon console, click New Project. Choose a region close to your deployment environment, give the project a name (e.g. storx), and confirm. Neon automatically provisions a default database named neondb.
3

Copy the connection string

In the project dashboard, navigate to Connection Details. Select the Pooled connection string for application use. It looks like:
postgresql://user:password@ep-cool-name-123456.us-east-2.aws.neon.tech/neondb?sslmode=require
Make sure sslmode=require is present — Neon requires SSL for all connections.
4

Set DATABASE_URL in .env.local

Paste the connection string into your .env.local file:
.env.local
DATABASE_URL=postgresql://user:password@ep-cool-name-123456.us-east-2.aws.neon.tech/neondb?sslmode=require
The Drizzle config reads this variable at startup and throws if it is absent:
drizzle.config.ts
import * as dotenv from "dotenv";
import { defineConfig } from "drizzle-kit";

dotenv.config({ path: ".env.local" });

if (!process.env.DATABASE_URL) {
  throw new Error("Database url is not set in .env.local");
}

export default defineConfig({
  out: "./drizzle",
  schema: "./lib/db/schema.ts",
  dialect: "postgresql",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
  migrations: {
    table: "__drizzle_migration",
    schema: "public",
  },
  verbose: true,
  strict: true,
});

Database Schema

Storx uses a single files table that represents both files and folders. Folders are differentiated from files via the is_folder boolean flag, and the parent_id column enables an arbitrary-depth folder hierarchy through a self-referential foreign key relationship. The initial migration SQL that creates this table is:
drizzle/0000_woozy_exodus.sql
CREATE TABLE "files" (
	"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
	"name" text NOT NULL,
	"size" integer NOT NULL,
	"type" text NOT NULL,
	"file_url" text NOT NULL,
	"thumbnail_url" text,
	"user_id" text NOT NULL,
	"parent_id" uuid,
	"is_folder" boolean DEFAULT false NOT NULL,
	"is_starred" boolean DEFAULT false NOT NULL,
	"is_trash" boolean DEFAULT false NOT NULL,
	"created_at" timestamp DEFAULT now() NOT NULL,
	"updated_at" timestamp DEFAULT now() NOT NULL
);

Column Groups

GroupColumnsPurpose
IdentityidUUID primary key, auto-generated with gen_random_uuid()
Basic infoname, size, typeDisplay name, byte size, and MIME type (or "folder" for folders)
Storagefile_url, thumbnail_urlPublic ImageKit delivery URL and optional thumbnail URL
Ownershipuser_id, parent_idClerk user ID of the owner; UUID of the parent folder (null for root items)
Flagsis_folder, is_starred, is_trashDistinguish folders from files; track starred and trashed states
Timestampscreated_at, updated_atAuto-set on insert; updated_at must be manually refreshed on writes
The full Drizzle schema definition, including the self-referential parent/children relation, lives in lib/db/schema.ts:
lib/db/schema.ts
import {
  pgTable,
  text,
  timestamp,
  uuid,
  integer,
  boolean,
} from "drizzle-orm/pg-core";

import { relations } from "drizzle-orm";

export const files = pgTable("files", {
  id: uuid("id").defaultRandom().primaryKey(),

  // basic file/folder info
  name: text("name").notNull(),
  size: integer("size").notNull(),
  type: text("type").notNull(), // "folder" for directories

  // storage information
  fileUrl: text("file_url").notNull(),
  thumbnailUrl: text("thumbnail_url"),
  imagekitFileId: text("imagekit_file_id"),

  // ownership
  userId: text("user_id").notNull(),
  parentId: uuid("parent_id"), // null for root-level items

  // file/folder flags
  isFolder: boolean("is_folder").default(false).notNull(),
  isStarred: boolean("is_starred").default(false).notNull(),
  isTrash: boolean("is_trash").default(false).notNull(),

  // timestamps
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull(),
});

export const fileRelation = relations(files, ({ one, many }) => ({
  // each file/folder can have one parent folder
  parent: one(files, {
    fields: [files.parentId],
    references: [files.id],
    relationName: "parent",
  }),
  // relationship to child files/folders
  children: many(files, {
    relationName: "parent",
  }),
}));

Running Migrations

Storx ships three database scripts. Choose the one that matches your workflow:
npm run db:migrate
CommandWhat it does
npm run db:migrateExecutes lib/db/migrate.ts using tsx, which applies all pending SQL migration files from the ./drizzle folder against your Neon database. Use this in CI/CD and production deployments.
npm run db:pushRuns drizzle-kit push to diff the current schema against the live database and apply changes directly — without generating a migration file. Ideal for rapid iteration during local development.
npm run db:generateRuns drizzle-kit generate to produce a new SQL migration file under ./drizzle whenever you change lib/db/schema.ts. Commit the generated file alongside your schema change.

Drizzle Studio

Drizzle Kit ships a browser-based database GUI called Drizzle Studio. Run it locally to browse and edit rows without writing SQL:
npm run db:studio
This starts a local server (typically at https://local.drizzle.studio) and opens a visual interface connected to your Neon database via DATABASE_URL.

Schema Types

Drizzle infers TypeScript types directly from the table definition. Storx exports two types from lib/db/schema.ts for use in API routes and server actions:
lib/db/schema.ts
// Type of a row returned by SELECT queries
export const FileType = typeof files.$inferSelect;

// Type of a row accepted by INSERT queries
export const NewFileTYpe = typeof files.$inferInsert;
FileType represents a fully hydrated database row (all columns present). NewFileTYpe represents the shape of data you pass to db.insert(files).values(...) — optional columns like thumbnailUrl, imagekitFileId, and parentId are typed as optional in this variant.

Build docs developers (and LLMs) love