# Implementing Mongoose Models with Next.js in TypeScript

# Non Next.js application
In a typical Express JS project the below code would have sufficed 
```TypeScript
// test.model.ts
import { model, Model, Schema } from "mongoose";
import createModel from "../createModel";

interface ITest {
	first_name: string;
	last_name: string;
}

interface ITestMethods {
	fullName(): string;
}

type TestModel = Model<ITest, {}, ITestMethods>;

const testSchema = new Schema<ITest, TestModel, ITestMethods>({
	first_name: String,
	last_name: String,
});

testSchema.method("fullName", function fullName() {
	return this.first_name + " " + this.last_name;
});

export default model<ITest, TestModel>("tests", testSchema);
```

But if the code were to be used for development in Next.js you would get the following error
```Bash
OverwriteModelError: Cannot overwrite `tests` model once compiled
```

# Implementation for Next.js
To use Mongoose with Next JS during development requires some roundabout implementation of creating models due to the HMR (Hot Module Replacement) done by Next JS.

## Common implementation found
```javascript
module.exports = mongoose.models.User || mongoose.model("User", UserSchema);
```
One problem with the implementation is that it is a JavaScript approach and while it works in TypeScript with no error, it does not make use of the TypeScript type system. 

## My implementation
The implementation used the Next.js recommended way to connect to MongoDB as reference. [Next.js code](https://github.com/vercel/next.js/blob/canary/examples/with-mongodb/lib/mongodb.js)
``` TypeScript
// lib/createModel.ts
import { Model, model, Schema } from "mongoose";

// Simple Generic Function for reusability
// Feel free to modify however you like
export default function createModel<T, TModel = Model<T>>(
	modelName: string,
	schema: Schema<T>
): TModel {
	let createdModel: TModel;
	if (process.env.NODE_ENV === "development") {
		// In development mode, use a global variable so that the value
		// is preserved across module reloads caused by HMR (Hot Module Replacement).
		// @ts-ignore
		if (!global[modelName]) {
			createdModel = model<T, TModel>(modelName, schema);
			// @ts-ignore
			global[modelName] = createdModel;
		}
		// @ts-ignore
		createdModel = global[modelName];
	
	} else {
		// In production mode, it's best to not use a global variable.
		createdModel = model<T, TModel>(modelName, schema);
	}
	return createdModel;
}

```

```TypeScript
// lib/models/test.model.ts
import { Model, Schema } from "mongoose";
import createModel from "../createModel";

interface ITest {
	first_name: string;
	last_name: string;
}

interface ITestMethods {
	fullName(): string;
}


type TestModel = Model<ITest, {}, ITestMethods>;

const testSchema = new Schema<ITest, TestModel, ITestMethods>({
	first_name: String,
	last_name: String,
});

testSchema.method("fullName", function fullName() {
	return this.first_name + " " + this.last_name;
});

export default createModel<ITest, TestModel>("tests", testSchema);

```

```TypeScript
// pages/api/test.ts
import { connection, connect } from "mongoose";
import type { NextApiRequest, NextApiResponse } from "next";
import testModel from "../../lib/models/test.model";

const uri: string = process.env.MONGODB_URI || "";

export default async function test(
	req: NextApiRequest, 
	res: NextApiResponse
) {
	try {
		await connect(uri);
		const testObject = new testModel({ 
			first_name: "Tom", 
			last_name: "Jerry" 
		});
		// The intellisense will detect the fullName Method
		const name = testObject.fullName();
		await testObject.save();
		
		res.status(201).json({
			name,
		});
		
		// Erase test data after use
		connection.db.dropCollection(
			testModel.collection.collectionName
		);
		
	} catch (err) {
		res.status(400).json(err);
	}
}
```

Simply make any REST API request to the /api/test route to see the code in action
[Source Code](https://stackblitz.com/edit/node-ogka1w)
