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.

Frontend Engineer at PrivCo

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store