Skip to content
Back to Blog
By JSONConvert Team··5 min read

JSON to Mongoose Schema

You have a JSON response from an API and you need to model it in MongoDB with Mongoose. Writing the schema by hand means looking up SchemaTypes, deciding which fields are required, figuring out how to handle nested objects, and doing this for every field in the payload. For a response with 20 fields, that's 20 opportunities to get a type wrong.

The faster path is to generate the scaffold from your JSON, then refine it. Here's how both parts work.

Mongoose SchemaTypes: Mapping from JSON

JSON has six primitive types. Mongoose has more. The mapping is mostly obvious, but a few cases need decisions.

JSON typeMongoose SchemaType
stringString
number (integer)Number
number (float)Number
booleanBoolean
nulloptional field (omit required)
objectnested schema or Mixed
array[SchemaType]
The Mixed type accepts any value - it's a trap. If a field is consistently a string but sometimes null, make it String with required: false instead of Mixed. Reserve Mixed for genuinely dynamic data like audit log payloads where you can't predict the shape.

A basic schema from a user API response:

Notice that created_at maps to Date, not String. Mongoose automatically coerces ISO 8601 strings to Date objects during document creation. If your API returns a Unix timestamp (e.g., 1705312200), Date still works - Mongoose handles the conversion.

Nested Documents and Arrays

Two cases come up constantly when working with API responses: nested objects and arrays of objects.

Nested object - define an inline sub-schema:

Inline sub-schemas work fine when the shape is used in only one place. If address appears in multiple models, extract it to a shared schema and use type: addressSchema.

Array of objects - use square brackets around the sub-schema:

Mongoose creates a default _id for each subdocument in the array. If you don't need it, add _id: false to the sub-schema options: items: [{ _id: false, sku: String, ... }].

Required Fields, Defaults, and Validators

Not every JSON field should be required: true. The JSON response you're modeling might come from an endpoint that returns optional fields depending on the query. A few rules:

  • If the API always returns the field, mark it required: true
  • If the field can be absent or null, leave required out and optionally set a default
  • If the field is sometimes an empty string, add minlength: 1 so empty strings fail validation

The enum validator is worth adding whenever a JSON field is a finite set of string values. If a future API response sends an unexpected value, the document save fails with a clear validation error instead of silently storing bad data.

Indexes

Schema definition is where you add indexes. Mongoose creates them on connection via syncIndexes (or the older ensureIndexes). The two you reach for most often:

unique: true creates a unique index. It does not enforce uniqueness at the Mongoose layer - it relies on MongoDB's index to reject duplicate inserts. Use it only for fields where the database-level constraint is appropriate, not as a shortcut for application validation.

Generate a Schema from Real JSON

Manually mapping a 30-field API response to Mongoose SchemaTypes is where typos happen. The faster path: paste your JSON into the Mongoose schema generator, get a scaffold in seconds, then spend your time on the decisions that actually require judgment - which fields are required, what the valid enum values are, which fields need indexes.

The generator handles nested objects, arrays of primitives, arrays of objects, and ISO 8601 strings (mapped to Date). After generating, the main things to add by hand are required: true for fields the API always returns, enum for string fields with a fixed set of values, ref for ObjectId fields that reference other collections, and indexes for fields you filter or sort on.

If you need to check the JSON is valid before generating, run it through the JSON validator first. For TypeScript projects, you can generate both a Mongoose schema and a TypeScript interface from the same JSON - the interface handles compile-time checks while the schema handles runtime validation.

One thing the generator won't decide for you: whether a nested object should be an inline sub-schema or a separate model with a reference. That depends on whether the data is always queried together. If you need the nested object independently, it belongs in its own collection. If you always fetch it with the parent, inline it.

Related Tools