Next.js 10 + MikroOrm
Recently I’ve been experimenting with NextJS. Something that I found very compelling was the built in api
page routing. In theory, you can build a fully featured JAMStack app using just Next.js and deploy it to serverless.
While I know many people in practice use the api
routes to proxy requests to external apis, it seems to me that if you wanted to quickly bootstrap an application, you should be able to use an ORM and Database (for me it’s MySQL) of your choice to get up and running. Coming from a Laravel background, I was looking for a solution which reminded me of their Eloquent ORM, or Doctrine, which I use at work. Basic features should be schema generation, migration generation and running, and filtering / query builder.
My first attempts at getting this concept up and running used the popular TypeORM. At first glance, this seems to be the best option. However, in practice, I found the documentation to be sub-par and the support for serverless environments to be poor. I ran into lots of issues with hot module reloading, and reusing the connection to MySQL. If you Google “TypeORM and Next.js”, you will find countless Github issues with no clear resolution.
Around this time, I stumbled across MikroOrm, which seemed to have the same feature set as TypeORM that I was looking for, and a clean API syntax. You can read the authors motivations and comparisons here.
Step 1 — Creating a new project and install MikroORM
Your first step is to create a new next.js project with MikroOrm.
npx create-react-app my-new-project
...npm i @mikro-orm/core @mikro-orm/mysql @mikro-orm/cli reflect-metadata
Step 2 — Configuring Babel and TypeScript
Our next step is to get TypeScript working in the project. On the command line run
touch tsconfig.json
Following this, you should start your dev server by running
npm run dev
It will error out telling you to install some TS type packages. Install those and then restart the server. It should succeed.
While Next.js does have TS support, you need to do a little extra work to get the @Decorator
syntax working. In the root of your project create a file called .babelrc
. Copy the following in there:
{ "presets": ["next/babel"], "plugins": [ "babel-plugin-transform-typescript-metadata", ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }] ]}
We now need to also install those packages:
npm i -D babel-plugin-transform-typescript-metadata @babel/plugin-proposal-decorators
Note: @babel/plugin-proposal-class-properties comes installed already so we don’t need to install again. If you have issues with it, just
npm i @babel/plugin-proposal-class-properties
We need an extra tsconfig file, specifically for the ORM. This is because the JS target version for the CLI is requires syntax that the ES5 default target for Next.js does not support. Do this by creating a file called tsconfig.orm.json
. Inside, place the following:
{ "extends": "./tsconfig.json", "compilerOptions": { "module": "commonjs", "target": "es6" }}
Step 3 — Create an entity
Create a folder called entities
in your root directory. In this example, we are going to create a simple User
entity. Inside, create a file called User.ts
. Place the following inside:
import {PrimaryKey,Entity,Property,Unique,} from "@mikro-orm/core";@Entity()export class User { @PrimaryKey() id!: number;
@Property() name!: string; @Property() @Unique() email: string; @Property({ nullable: true }) age?: number; @Property({ nullable: true }) born?: Date; constructor(name: string, email: string) { this.name = name; this.email = email; }}
Step 4— Configure MikroOrm
In your root directory, create a file called mikro-orm.config.ts
Place the following inside:
import dotenv from "dotenv";dotenv.config();import config from "./config/mikro-orm";export default config;
You see a reference to ./config/mikro-orm
. We need to create that file as well now. In the root directory, create a folder called config
and place a file called mikro-orm.ts
inside. The contents of the file are your MikroOrm config, and should be roughly as follows:
import { User } from "../entities/User";import { Options } from "@mikro-orm/core";const config: Options = { dbName: "database-name", type: "mysql", host: process.env.MYSQL_HOST, port: Number(process.env.MYSQL_PORT), user: process.env.MYSQL_USERNAME, password: process.env.MYSQL_PASSWORD, entities: [User], discovery: { disableDynamicFileAccess: false }, debug: process.env.NODE_ENV === "development",};export default config;
Place anything referenced by
process.env.MYSQL_
inside a file called .env. In the example repository I have create one that can be filled out.
Our last step is to configure our package.json
to use the new tsconfig.orm.json
that we made. The top of mine looks something like this:
{ "name": "mikro-orm-nextjs", "version": "0.1.0", "private": true, "mikro-orm": { "useTsNode": true, "tsConfigPath": "./tsconfig.orm.json", "configPaths": [ "./config/mikro-orm.ts" ] }
...
}
To check if everything you’ve done at this point is working, i’d recommend running npx mikro-orm debug
in the terminal. Your output should contain all the configuration thus far, and not contain any errors.
Step 5 — Using MikroOrm: A contrived example
Once you have your entity set up and the CLI tool working, you need to start working with your database. To do this, we need to create our initial migration. This is done by using the CLI tool to create our first migration:
npx mikro-orm migration:create --initial
A folder named migrations should appear in your root directory, with a file containing the migration logic. Example output:
Now that we have successfully create our migration, we need to run it to create changes to the database. This accomplished by running
npx mikro-orm migration:up
Once that successfully completes, your database should contain an empty table named users, with the columns id, name, email, age, born
.
To use our new database table and create queries, we head back into Next.js. Create a folder in pages called api
and create a new file called users.js
with the following inside:
import 'reflect-metadata';
import { MikroORM } from "@mikro-orm/core";
import config from "../../config/mikro-orm";
import { User } from "../../entities/User";
export default async (req, res) => { const orm = await MikroORM.init(config); const users = await orm.em.find(User); res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({ user: users }));};
Returned should be an empty. If you wanted to create another API route which contains a user, the syntax would be something like:
const user = new User("Example User", "example@example.com")
await orm.em.persistAndFlush([user]);
At this point, however, you should be up and running with MikroOrm, whatever you do with it at this point is up to you. If you read the fantastic MikroOrm Docs you should have no problem creating your own API and frontend!
If you want to use my repo as a starting place, feel free to clone it and modify or fork. If you see anything wrong in this repo, feel free to reach out on Github or comment on here.