gadget/docs/gadget-id.md

3.1 KiB

GadgetId

Overview

GadgetId is a string-based identifier type used throughout the Gadget monorepo for all entity IDs. It replaces MongoDB's native ObjectId type with a simple, portable string that works consistently across all packages without requiring Mongoose or MongoDB-specific conversions.

Type Definition

// packages/api/src/lib/gadget-id.ts
export type GadgetId = string;

Why GadgetId?

  • Consistency: All packages use the same ID type regardless of whether they use Mongoose
  • Simplicity: No createFromHexString() or toHexString() conversions needed
  • Portability: Works seamlessly from backend to frontend to drone workers
  • Type safety: Compile-time checking ensures IDs are always strings

Usage

Interfaces

Always use GadgetId for _id fields in interfaces:

import { GadgetId } from "@gadget/api";

interface IUser {
  _id: GadgetId;
  email: string;
  displayName: string;
}

Mongoose Schemas

Use the following pattern for all schemas:

import { Schema, model } from "mongoose";
import { GadgetId } from "@gadget/api";
import { nanoid } from "nanoid";

const MySchema = new Schema<IMyInterface>({
  _id: { type: String, default: () => nanoid() },
  // ... other fields
});

Important Schema Rules

Do NOT add unique: true or required: true to the _id field:

  • MongoDB automatically creates a unique index on _id; specifying unique: true causes duplicate index errors at startup
  • MongoDB handles _id validation automatically; specifying required: true causes save failures because the default is assigned AFTER validation

Correct:

_id: { type: String, default: () => nanoid() }

Incorrect:

// DON'T do this - breaks everything
_id: { type: String, default: () => nanoid(), unique: true, required: true }

Creating Documents

const thing = new Thing({ /* other fields */ });
await thing.save(); // Works - MongoDB handles _id automatically

Passing IDs to Functions

IDs are strings - just pass them directly:

const userId: GadgetId = user._id;
await userService.getById(userId); // No conversion needed

GadgetId vs ObjectId

Operation ObjectId GadgetId
Type ObjectId string
Creation new ObjectId() nanoid() (via schema default)
String conversion id.toHexString() Not needed
Parsing from string createFromHexString(str) Not needed
Mongoose casting Automatic with Schema.Types.ObjectId Automatic with type: String

Importing GadgetId

// From @gadget/api (recommended for most code)
import { GadgetId } from "@gadget/api";

// From packages/api directly (for @gadget/api consumers)
import { GadgetId } from "../../../packages/api/dist/lib/gadget-id.js";

Rules

  1. Never use ObjectId directly in application code
  2. Never import ObjectId from Mongoose in service/controller code
  3. Always use GadgetId for _id fields in interfaces and function parameters
  4. Never add unique or required to _id in Mongoose schemas