> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modelence.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Modules

> Organize your backend into self-contained units with queries, mutations, stores, and configuration

Modules are the fundamental building blocks of a Modelence application. They help you organize your backend functionality into cohesive, self-contained units that encapsulate queries, mutations, stores, and configuration.

## What is a Module?

A Module in Modelence is similar to a feature module in other frameworks. It groups related functionality together, making your codebase more maintainable and easier to reason about.

```typescript theme={null}
import { Module } from 'modelence/server';

export default new Module('todo', {
  // Module configuration goes here
});
```

## Module Structure

A typical module includes:

* **Stores** - MongoDB collection definitions
* **Queries** - Read operations that fetch data
* **Mutations** - Write operations that modify data
* **Configuration** - Module-specific settings
* **Cron Jobs** - Scheduled tasks (optional)

<Note>
  Data migrations are configured at the `startApp()` level (not inside individual
  modules). See the [Migrations documentation](/core-concepts/migrations).
</Note>

## Stores

Stores define your MongoDB collections with schemas, indexes, and custom methods. Including stores in your module ensures they're automatically provisioned when the server starts.

```typescript theme={null}
import { Store, schema } from 'modelence/server';

export const dbTodos = new Store('todos', {
  schema: {
    title: schema.string(),
    isCompleted: schema.boolean(),
    userId: schema.userId(),
    createdAt: schema.date()
  },
  indexes: [
    { key: { userId: 1 } }
  ]
});

export default new Module('todo', {
  // Register the store with the module
  stores: [dbTodos],

  queries: {
    async getAll({}, { user }) {
      return await dbTodos.fetch({ userId: user.id });
    }
  }
});
```

<Tip>
  Learn more about working with Stores in the [Stores documentation](/stores).
</Tip>

## Authentication & Authorization

You can restrict access to queries and mutations using authentication requirements:

```typescript theme={null}
export default new Module('todo', {
  queries: {
    getAll: {
      // Require authentication for this query
      auth: true,
      async handler({}, { user }) {
        // user is guaranteed to exist here
        return await dbTodos.fetch({ userId: user.id });
      }
    },

    getPublic: {
      // No authentication required
      auth: false,
      async handler() {
        return await dbTodos.fetch({ isPublic: true });
      }
    }
  },

  mutations: {
    adminDelete: {
      // Custom authorization check
      auth: true,
      authorize: ({ user }) => {
        if (!user.roles?.includes('admin')) {
          throw new Error('Admin access required');
        }
      },
      async handler({ id }) {
        return await dbTodos.deleteOne({ _id: new ObjectId(id) });
      }
    }
  }
});
```

## Queries

Queries are read operations that fetch data without modifying state.

For full query patterns, including client usage with `callMethod`,
`modelenceQuery`, and typed client modules, see
[Queries](/core-concepts/queries).

## Mutations

Mutations are write operations that create, update, or delete data.

For full mutation patterns, including client usage with `callMethod`,
`modelenceMutation`, and typed client modules, see
[Mutations](/core-concepts/mutations).

## Rate Limiting

Protect your queries and mutations from abuse by declaring rate limit rules on the module and consuming them inside handlers:

```typescript theme={null}
import { Module, consumeRateLimit } from 'modelence/server';
import { time } from 'modelence/server';

export default new Module('todo', {
  rateLimits: [
    { bucket: 'todoCreate', type: 'ip', window: time.minutes(1), limit: 10 },
  ],

  mutations: {
    async create({ title }, { user, connectionInfo }) {
      await consumeRateLimit({
        bucket: 'todoCreate',
        type: 'ip',
        value: connectionInfo.ip,
        message: 'Too many todos created. Please slow down.',
      });

      const { insertedId } = await dbTodos.insertOne({
        title,
        userId: user.id,
        isCompleted: false,
        createdAt: new Date()
      });
      return insertedId;
    }
  }
});
```

<Tip>
  Learn more about rate limiting in the [Custom Rate Limiting documentation](/rate-limiting).
</Tip>

## Best Practices

### 1. Keep Modules Focused

Each module should represent a single domain or feature:

```typescript theme={null}
// Good: Focused todo module
export default new Module('todo', {
  // Only todo-related functionality
});

// Good: Separate user module
export default new Module('users', {
  // Only user-related functionality
});
```

### 2. Use Clear Naming

Name your queries and mutations descriptively:

```typescript theme={null}
// Good
queries: {
  getAll() { },
  getByStatus({ status }) { },
  getOverdue() { }
}

// Avoid
queries: {
  get() { },           // Too generic
  fetchData() { },     // Unclear what data
  q1() { }            // Meaningless name
}
```

### 3. Handle Errors Gracefully

Always handle potential errors and provide meaningful messages:

```typescript theme={null}
mutations: {
  async delete({ id }, { user }) {
    const todo = await dbTodos.findById(id);

    if (!todo) {
      throw new Error('Todo not found');
    }

    if (todo.userId !== user.id) {
      throw new Error('Not authorized to delete this todo');
    }

    return await dbTodos.deleteOne({ _id: new ObjectId(id) });
  }
}
```

### 4. Keep Business Logic in Modules

Don't put business logic directly in your database stores. Keep it in your module methods:

```typescript theme={null}
// Good: Business logic in module
export default new Module('todo', {
  mutations: {
    async complete({ id }, { user }) {
      const todo = await dbTodos.findById(id);

      // Business logic
      if (todo.isCompleted) {
        throw new Error('Todo is already completed');
      }

      await dbTodos.updateOne(id, {
        $set: {
          isCompleted: true,
          completedAt: new Date()
        }
      });

      // Trigger side effects
      await callMethod('notifications.send', {
        userId: user.id,
        message: 'Todo completed!'
      });
    }
  }
});
```

### 5. Use TypeScript Types

Leverage TypeScript for type safety across your modules:

```typescript theme={null}
import { schema } from 'modelence/server';

type TodoPriority = 'low' | 'medium' | 'high';

export default new Module('todo', {
  mutations: {
    create: {
      input: {
        title: schema.string(),
        priority: schema.enum<TodoPriority>(['low', 'medium', 'high'])
      },
      async handler(input, { user }) {
        // input is fully typed
        const { insertedId } = await dbTodos.insertOne({
          ...input,
          userId: user.id,
          createdAt: new Date()
        });
        return insertedId;
      }
    }
  }
});
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Queries" icon="magnifying-glass" href="/core-concepts/queries">
    Learn how to define and call query methods
  </Card>

  <Card title="Mutations" icon="pen-to-square" href="/core-concepts/mutations">
    Learn how to define and call mutation methods
  </Card>

  <Card title="Migrations" icon="arrows-rotate" href="/core-concepts/migrations">
    Learn how migration scripts run and how to handle cron race conditions
  </Card>

  <Card title="Configuration" icon="gear" href="/configuration">
    Learn about configuration options
  </Card>

  <Card title="Stores" icon="database" href="/stores">
    Deep dive into working with MongoDB stores
  </Card>
</CardGroup>
