/typebooxe

mongoose meets typebox

Primary LanguageTypeScriptMIT LicenseMIT

typebooxe

mongoose meets TypeBox

Defining a Schema with typebooxe

const Person = Type.Object({
  name: Type.String(),
  age: Type.Number(),
}, {
  $id: "Person", // Used for the mongoose collection name
});

type PersonType = Static<typeof Person>; // Define type for Person schema

const PersonModel = typebooxe<PersonType>(Person);

// ... Connect to your MongoDB instance

// Create a new person document
const person = await PersonModel.create({
  name: "aberigle",
  age: 34,
});

console.log(person); // Document type (includes Mongoose-specific properties)
// Output: {
//   name: "aberigle",
//   age: 34,
//   _id: new ObjectId('670e8b0c500875615df28cac'),
// }

console.log(person.cast()); // PersonType
// Output : {
//   name: "aberigle",
//   age: 34
// }

Calling .cast() turns the Mongoose document into a TypeBox-valiadble object, matching the defined schema

Supported Data Types

Type.String()   => { type : String, required : true }
Type.Number()   => { type : Number, required : true }
Type.Integer()  => { type : Number, required : true }
Type.Boolean()  => { type : Boolean, required : true }
Type.Date()     => { type : Date, required : true }
Type.Any()      => { type : Schema.Types.Mixed, required : true }

Objects and Arrays

Embed documents can be defined with Type.Object()

Type.Object({              {
  field : Type.String() =>    field : { type : String, required : true }
})                         }

Additionally, arrays can be defined with Type.Array()

Type.Array(Type.String()) => [{ type : String, required : true }]
Type.Array(Type.Object({     [{
  field : Type.String()   =>    field : { type : String, required : true }
}))                          }]

Optional fields

By default, all fields are required unless wrapped in Type.Optional()

Type.Optional(Type.String()) => { type : String, required : false}

Enums

Enums are defined by typescript a enum and Type.Enum()

enum JobTypes {
  developer = "developer",
  designer  = "designer",
  manager   = "manager"
}

Type.Enum(JobTypes) => { type : String, enum : ["developer", "designer", "manager"]}

Handling the _id Field

mongodb automatically generates an _id field for each document. If you don't explicitly define it in the schema, it won't be serialized.

const Person = Type.Object({
  id : Type.String() // this will have the mongo _id
})

Defining References Between Models

TypeBox allows you to define relationships between models using Type.Ref(). This function creates a reference type that maps to mongoose references.

Here's an example of defining a Person with a reference to a Job model:

const Job = Type.Object({
  name: Type.String(),
}, {
  $id: "Job",
});

const JobModel = typebooxe<Static<typeof Job>>(Job);

const Person = Type.Object({
  name: Type.String(),
  job: Type.Ref(Job), // Reference to the Job model
}, {
  $id: "Person",
});

const PersonModel = typebooxe<Static<typeof Person>>(Person);

const person = await PersonModel.findOne({
  name : "aberigle"
}).populate("job")

person.cast()
// Type: Static<typeof Person>
// {
//   name : "aberigle",
//   job : { // Type: Static<typeof Job>
//     name : "developer"
//   }
// }

Making References Optional

There are two ways to make references optional:

  1. Union with String: Define the field as a union of Type.Ref(Job) and Type.String(). This allows to cast to the referenced model or the objectid's string value in the job field.
const Person = Type.Object({
  name : Type.String(),
  job  : Type.Union([ Type.Ref(Job), Type.String() ])
}, {
  $id : "Person"
})
  1. Optional Reference: Use Type.Optional(Type.Ref(Job)). This ensures the .job field won't exist altogether in the casted object if not populated
const Person = Type.Object({
  name : Type.String(),
  job  : Type.Optional(Type.Ref(Job))
}, {
  $id : "Person"
})