Skip to content

Latest commit

 

History

History
434 lines (343 loc) · 10.9 KB

File metadata and controls

434 lines (343 loc) · 10.9 KB

Feature: JSON Schema Import

Overview

The aapi import command allows you to automatically generate Mongoose models, GraphQL schemas, and resolvers from existing JSON data (like MongoDB exports or any JSON file).

Motivation

Problem: Manually creating models for existing databases is tedious and error-prone.

Solution: Analyze JSON data structure and automatically generate all necessary code.

Usage

Basic Usage

# Import from JSON file (uses filename as model name)
aapi import users.json

# Specify custom model name
aapi import data.json --name Product

# Preview without creating files
aapi import users.json --preview

# Force overwrite existing files
aapi import users.json --force

Real-World Workflow

# 1. Export from MongoDB
mongoexport --collection=users --db=mydb --out=users.json --jsonArray

# 2. Preview the schema
aapi import users.json --preview

# Output:
# 📊 Schema Summary:
#   Model name: User
#   Total fields: 7
#   Required fields: 6
#   Optional fields: 1
#
# 📋 Detected Fields:
#   username: String [required]
#   email: String [required]
#   age: Int [required]
#   ...

# 3. Create the files
aapi import users.json

# Output:
# ✅ Files created:
#   - src/models/User.js
#   - src/graphql/typeDefs/User.graphql
#   - src/graphql/resolvers/UserResolver.js

Features

Automatic Type Detection

The schema parser analyzes your JSON data and automatically detects:

JSON Type Mongoose Type GraphQL Type Notes
"string" String String -
42 Number Int Integer numbers
3.14 Number Float Decimal numbers
true Boolean Boolean -
"2024-01-15T..." Date Date ISO date strings
["a", "b"] [String] [String] Arrays (element type detected)
{"key": "val"} Mixed JSON Nested objects
"507f1f77bcf86..." ObjectId ID 24-char hex (MongoDB ID)

Smart Field Analysis

  • Samples multiple documents: Analyzes up to 10 documents to determine types
  • Required field detection: Fields present in >80% of samples are marked as required
  • Consistent typing: Ensures type consistency across samples
  • Array element detection: Detects array element types from first element

Generated Code

For each model, AAPI generates:

1. Mongoose Model (src/models/User.js)

import mongoose from 'mongoose';

const UserSchema = new mongoose.Schema(
  {
    username: { type: String, required: true },
    email: { type: String, required: true },
    age: { type: Number, required: true },
    isActive: { type: Boolean, required: true },
    tags: { type: [String], required: true },
    // ...
  },
  {
    timestamps: true,
  }
);

export default mongoose.model('User', UserSchema);

2. GraphQL Schema (src/graphql/typeDefs/User.graphql)

type User {
  _id: ID!
  username: String!
  email: String!
  age: Int!
  isActive: Boolean!
  tags: [String]!
  createdAt: Date
  updatedAt: Date
}

input UserInput {
  username: String!
  email: String!
  age: Int!
  isActive: Boolean!
  tags: [String]!
}

type Query {
  users: [User!]!
  user(id: ID!): User
}

type Mutation {
  createUser(input: UserInput!): User
  updateUser(id: ID!, input: UserInput!): User
  deleteUser(id: ID!): Boolean!
}

3. Resolvers (src/graphql/resolvers/UserResolver.js)

import User from '../../models/User.js';

export default {
  Query: {
    users: async () => User.find().lean(),
    user: async (_, { id }) => {
      const result = await User.findById(id).lean();
      if (!result) throw new Error('User not found');
      return result;
    },
  },
  Mutation: {
    createUser: async (_, { input }) => User.create(input),
    updateUser: async (_, { id, input }) => {
      const result = await User.findByIdAndUpdate(id, input, { new: true, lean: true });
      if (!result) throw new Error('User not found');
      return result;
    },
    deleteUser: async (_, { id }) => {
      const result = await User.findByIdAndDelete(id);
      return !!result;
    },
  },
};

Implementation

New Files

  1. src/utils/schema-parser.js - Core parsing logic

    • analyzeSchema() - Analyzes JSON and extracts field types
    • inferMongooseType() - Maps JS types to Mongoose types
    • inferGraphQLType() - Maps JS types to GraphQL types
    • generateMongooseSchema() - Generates Mongoose schema code
    • generateGraphQLType() - Generates GraphQL type definitions
    • generateResolvers() - Generates resolver code
    • parseAndGenerate() - Main entry point
  2. src/commands/import.js - CLI command

    • Reads JSON file
    • Validates model name
    • Generates code using schema-parser
    • Creates files or shows preview

CLI Integration

Added to bin/cli.js:

program
  .command('import <file>')
  .description('Import models from JSON schema file')
  .option('-n, --name <name>', 'Custom model name (defaults to filename)')
  .option('-f, --force', 'Overwrite existing files if they exist')
  .option('-p, --preview', 'Preview generated code without creating files')
  .action((file, options) => importSchema(file, options));

Examples

Example 1: Simple User Schema

Input (examples/users.json):

[
  {
    "username": "john_doe",
    "email": "john@example.com",
    "age": 28,
    "isActive": true,
    "tags": ["developer", "nodejs"]
  }
]

Command:

aapi import examples/users.json --preview

Output:

📊 Schema Summary:
  Model name: User
  Total fields: 5
  Required fields: 5
  Optional fields: 0

📋 Detected Fields:
  username: String [required]
    Sample: "john_doe"
  email: String [required]
    Sample: "john@example.com"
  age: Int [required]
    Sample: 28
  isActive: Boolean [required]
    Sample: true
  tags: [String] [required]
    Sample: ["developer","nodejs"]

Example 2: Complex Product Schema

Input (examples/products.json):

[
  {
    "name": "Laptop Pro 15",
    "price": 1299.99,
    "stock": 45,
    "category": "Electronics",
    "isAvailable": true,
    "specifications": {
      "cpu": "Intel i7",
      "ram": "16GB"
    },
    "images": ["url1.jpg", "url2.jpg"],
    "ratings": [4.5, 4.8, 5.0]
  }
]

Detected Types:

  • name → String (required)
  • price → Float (required)
  • stock → Int (required)
  • category → String (required)
  • isAvailable → Boolean (required)
  • specifications → Mixed object (required)
  • images → [String] (required)
  • ratings → [Float] (required)

Use Cases

1. Migrating Existing Database

# Export all collections
mongoexport --collection=users --db=old_db --out=users.json --jsonArray
mongoexport --collection=products --db=old_db --out=products.json --jsonArray
mongoexport --collection=orders --db=old_db --out=orders.json --jsonArray

# Import into AAPI
aapi import users.json
aapi import products.json
aapi import orders.json

2. Rapid Prototyping

# Create sample data file
cat > posts.json << EOF
[
  {
    "title": "First Post",
    "content": "Hello World",
    "author": "John",
    "published": true,
    "views": 100
  }
]
EOF

# Generate model
aapi import posts.json

# Start server
npm run dev

3. API from JSON API Response

# Fetch data from external API
curl https://api.example.com/users > users.json

# Import into AAPI
aapi import users.json

Advantages

Time-saving: No need to manually write schemas ✅ Accurate: Types inferred from actual data ✅ Consistent: Generated code follows same patterns ✅ Preview: See what will be generated before committing ✅ Safe: Won't overwrite without --force flag ✅ Flexible: Supports complex nested structures ✅ Smart: Detects required vs optional fields

Limitations & Workarounds

Limitation Workaround
Nested objects → Mixed type Manually create nested schemas
No custom validation Add validators to generated model
No default values Add defaults to schema after generation
ObjectId refs need manual config Add ref property to schema
No indexes Add indexes to generated model
No virtual fields Add virtuals after generation
Arrays must be homogeneous Ensure consistent array element types

Future Enhancements

Potential improvements for future versions:

  • Support for TypeScript type definitions
  • Custom field mappings configuration
  • Support for JSON Schema format
  • Automatic relationship detection (foreign keys)
  • Index generation from sample queries
  • Validation rule inference (email, URL, etc.)
  • Support for MongoDB aggregation pipeline schemas
  • GraphQL directive generation
  • Pagination resolver generation
  • Filtering and sorting resolvers

Testing

Example test cases to add:

describe('schema-parser', () => {
  it('should detect string types', () => {
    const data = [{ name: 'John' }];
    const fields = analyzeSchema(data);
    expect(fields.name.mongooseType).toBe('String');
    expect(fields.name.graphqlType).toBe('String');
  });

  it('should detect required fields', () => {
    const data = [
      { name: 'John', age: 30 },
      { name: 'Jane', age: 25 },
      { name: 'Bob' }, // age missing
    ];
    const fields = analyzeSchema(data);
    expect(fields.name.required).toBe(true); // 100%
    expect(fields.age.required).toBe(false); // 66%
  });

  // ... more tests
});

Documentation

  • README: Usage examples added
  • examples/: Sample JSON files provided
  • examples/README.md: Detailed examples and workflows
  • FEATURE_IMPORT.md: This document

Performance

  • Parsing: O(n × m) where n = documents, m = fields
  • File I/O: Minimal (read once, write three files)
  • Memory: Samples limited to 10 documents to prevent memory issues

Security

  • ✅ Validates model names before file creation
  • ✅ Prevents path traversal attacks
  • ✅ Won't overwrite files without explicit --force
  • ✅ Sanitizes input before code generation

Compatibility

  • Node.js: >=18.0.0
  • MongoDB: Any version (works with JSON exports)
  • GraphQL: Compatible with Yoga and Apollo Server
  • Mongoose: >=8.x

Version: 0.2.0 Status: ✅ Complete and tested Author: AAPI Team License: MIT