# Mongoose services

Write a service class for each MongoDB collection with the mongoose plugin. Covers its schema and key, the model name, typing the services map, and reading a service from a handler.

Without services, every handler that looks up a user imports the model and writes its own `findOne` call. You copy the same query into several handlers, so changing how you look a user up means finding every copy.

A service keeps a collection's queries in one class. Extend [`MongooseService`](https://docs.seedcord.org/packages/plugin-mongoose/latest/mongoose-service), declare the schema as a static, and decorate the class with a key. Handlers then call its methods through that key.

```ts title="src/services/Users.ts"
import {
    MongooseService,
    RegisterMongooseService
} from '@seedcord/plugin-mongoose';
import mongoose from 'mongoose';

import type { MongooseDocument } from '@seedcord/plugin-mongoose';

interface IUser extends MongooseDocument {
    username: string;
    streak: number;
}

@RegisterMongooseService('users')
export class Users extends MongooseService<IUser> {
    public static schema = new mongoose.Schema<IUser>({
        username: { type: String, required: true, unique: true },
        streak: { type: Number, default: 0 }
    });

    public findByName(username: string): Promise<IUser | null> {
        return this.model.findOne({ username }).lean().exec();
    }
}

declare module '@seedcord/plugin-mongoose' {
    interface MongooseServices {
        users: Users;
    }
}
```

`Users.ts` does several things at once, so let's go through it one part at a time.

* **`IUser`** lists the fields a user document has. It extends [`MongooseDocument`](https://docs.seedcord.org/packages/plugin-mongoose/latest/mongoose-document), which declares `_id: string`, the one field every document has.
* **`@RegisterMongooseService('users')`** gives the class its key. Your handlers read the service from `services` under that key.
* **`MongooseService<IUser>`** is the base class. Passing `IUser` types `this.model` as `Model<IUser>`, so TypeScript checks the fields in every query on that model against `username` and `streak`. Without the type argument, the model's type only has `_id`.
* **`schema`** is the Mongoose schema the plugin builds the model from. Make sure it's `static`, because the decorator fails to compile without it.
* **`findByName`** keeps the lookup inside the class. A handler calls that method and never writes the query itself.
* **The `declare module` block** adds `users` to the keys the decorator accepts. [Typing the registry](#typing-the-registry) explains why you write it.

## You never construct a service

The constructor takes the plugin, the core, and a model. The plugin only has all three once it has connected, so it builds your services itself during startup. It imports every `.ts` and `.js` file under the `dir` you gave the [mongoose plugin](/plugins/mongoose), including subfolders. For each exported class that extends `MongooseService` and carries the decorator, it builds the model from `schema` and constructs the class. That constructor call adds the instance to the plugin's [`services`](https://docs.seedcord.org/packages/plugin-mongoose/latest/mongoose#services) map under its key.

> **Warning**
>
> If a class isn't exported or has no `@RegisterMongooseService`, the plugin skips it without throwing or logging anything. `services.users` then reads `undefined` even though TypeScript types it as `Users`.

## The model name sets the collection

The plugin uses your key as the Mongoose model name. Mongoose pluralizes that name to pick the collection, so a key of `user` reads and writes the `users` collection. That's fine for a new database. If your collection already exists under a different name, pass `modelName` so the model name can differ from the key.

```ts
declare module '@seedcord/plugin-mongoose' {
    interface MongooseServices {
        users: Users;
    }
}

@RegisterMongooseService('users', { modelName: 'app_user' })
export class Users extends MongooseService<IUser> {
    public static schema = new mongoose.Schema<IUser>({
        username: { type: String, required: true }
    });
}
```

With `modelName: 'app_user'`, mongoose reads the `app_users` collection, while your handlers still use the class as `services.users`. The decorator only accepts `'users'` because the `declare module` block adds it to `MongooseServices`. `modelName` takes any string, so your editor doesn't suggest one and TypeScript can't catch a misspelled model name.

## Typing the registry

[`MongooseServices`](https://docs.seedcord.org/packages/plugin-mongoose/latest/mongoose-services) starts empty, so the decorator doesn't accept any key until you declare one. The `declare module` block in `src/services/Users.ts` above adds `users`. The decorator then takes `'users'` and checks that the class matches `Users`, the type you declared for that key. If you pass `'guilds'` to the decorator before adding a `guilds` entry, TypeScript reports an error.

```ts
declare module '@seedcord/plugin-mongoose' {
    interface MongooseServices {
        users: Users;
        guilds: Guilds;
    }
}

RegisterMongooseService('
```

The decorator offers the two keys that `declare module` block adds to `MongooseServices`, `users` and `guilds`.

You add each entry by hand when you write a new service, since codegen doesn't write this block. It only writes the `Core` block from [Typing a plugin](/plugins/typing).

## Reading a service

A handler gets a service from the plugin's `services` map, under the key you declared.

```ts
const found =
    await this.core.db.services.users.findByName('ada');

await this.reply(
    found ? `Found ${found.username}.` : 'No record.'
);
```

`findByName` resolves to an `IUser` or `null`, so the handler checks `found` before it reads `username`.

If you read `services` before the plugin's `init()` has finished, it throws `PluginMongooseServicesNotReady`. Your handlers never read it that early, though, because they only start receiving interactions once `init()` has finished.
