Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: tests

on:
push:
branches: [ master, renovate/** ]
pull_request:
branches: [ master ]

jobs:
test:
name: Tests
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: [ 22, 24 ]
steps:
- name: Checkout Source code
uses: actions/checkout@v6

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}

- name: Enable corepack
run: |
corepack enable
corepack prepare yarn@stable --activate

- name: Install
run: yarn

- name: Build
run: yarn build

- name: Test
run: yarn test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ dist
temp
node_modules
.idea
.yarn
test.db
koa-test-db
yarn-error.log
1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
71 changes: 71 additions & 0 deletions app/controllers/author.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import request from 'supertest';
import { app, DI, init } from '../server';

describe('author controller', () => {

beforeAll(async () => {
await init;
DI.orm.config.set('dbName', 'koa-test-db');
DI.orm.config.getLogger().setDebugMode(false);
await DI.orm.config.getDriver().reconnect();
await DI.orm.schema.drop();
await DI.orm.schema.create();
});

afterAll(async () => {
await DI.orm.close(true);
DI.server.close();
});

it(`CRUD`, async () => {
let id;

await request(app.callback())
.post('/author')
.send({ name: 'a1', email: 'e1', books: [{ title: 'b1' }, { title: 'b2' }] })
.then(res => {
expect(res.status).toBe(200);
expect(res.body.id).toBeDefined();
expect(res.body.name).toBe('a1');
expect(res.body.email).toBe('e1');
expect(res.body.termsAccepted).toBe(false);
expect(res.body.books).toHaveLength(2);

id = res.body.id;
});

await request(app.callback())
.get('/author')
.then(res => {
expect(res.status).toBe(200);
expect(res.body).toHaveLength(1);
expect(res.body[0].id).toBeDefined();
expect(res.body[0].name).toBe('a1');
expect(res.body[0].email).toBe('e1');
expect(res.body[0].termsAccepted).toBe(false);
expect(res.body[0].books).toHaveLength(2);
});

await request(app.callback())
.get('/author/' + id)
.then(res => {
expect(res.status).toBe(200);
expect(res.body.id).toBeDefined();
expect(res.body.name).toBe('a1');
expect(res.body.email).toBe('e1');
expect(res.body.books).toHaveLength(2);
});

await request(app.callback())
.put('/author/' + id)
.send({ name: 'a2' })
.then(res => {
expect(res.status).toBe(200);
expect(res.body.id).toBeDefined();
expect(res.body.name).toBe('a2');
expect(res.body.email).toBe('e1');
expect(res.body.termsAccepted).toBe(false);
});
});

});
21 changes: 11 additions & 10 deletions app/controllers/author.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { QueryOrder } from '@mikro-orm/sqlite';
import { Context } from 'koa';
import Router from 'koa-router';

import { DI } from '../server';
Expand All @@ -8,17 +7,17 @@ import { z } from 'zod';

const router = new Router();

router.get('/', async (ctx: Context) => {
router.get('/', async ctx => {
ctx.body = await DI.authors.findAll({
populate: ['books'],
orderBy: { name: QueryOrder.DESC },
limit: 20,
});
});

router.get('/:id', async (ctx: Context) => {
router.get('/:id', async ctx => {
try {
const params = z.object({ id: z.number() }).parse(ctx.params);
const params = z.object({ id: z.coerce.number() }).parse(ctx.params);
const author = await DI.authors.findOne(params.id, { populate: ['books'] });

if (!author) {
Expand All @@ -32,13 +31,15 @@ router.get('/:id', async (ctx: Context) => {
}
});

router.post('/', async (ctx: Context) => {
if (!ctx.request.body.name || !ctx.request.body.email) {
router.post('/', async ctx => {
const body = (ctx.request as any).body;

if (!body.name || !body.email) {
return ctx.throw(400, { message: 'One of `name, email` is missing' });
}

try {
const author = DI.em.create(Author, ctx.request.body);
const author = DI.em.create(Author, body);
await DI.em.flush();

ctx.body = author;
Expand All @@ -48,16 +49,16 @@ router.post('/', async (ctx: Context) => {
}
});

router.put('/:id', async (ctx: Context) => {
router.put('/:id', async ctx => {
try {
const params = z.object({ id: z.number() }).parse(ctx.params);
const params = z.object({ id: z.coerce.number() }).parse(ctx.params);
const author = await DI.authors.findOne(params.id);

if (!author) {
return ctx.throw(404, { message: 'Author not found' });
}

DI.em.assign(author, ctx.request.body);
DI.em.assign(author, (ctx.request as any).body);
await DI.em.flush();

ctx.body = author;
Expand Down
60 changes: 60 additions & 0 deletions app/controllers/book.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import request from 'supertest';
import { app, DI, init } from '../server';

describe('book controller', () => {

beforeAll(async () => {
await init;
DI.orm.config.set('dbName', 'koa-test-db');
DI.orm.config.getLogger().setDebugMode(false);
await DI.orm.config.getDriver().reconnect();
await DI.orm.schema.drop();
await DI.orm.schema.create();
});

afterAll(async () => {
await DI.orm.close(true);
DI.server.close();
});

it(`CRUD`, async () => {
let id;

await request(app.callback())
.post('/book')
.send({ title: 'b1', author: { name: 'a1', email: 'e1' } })
.then(res => {
expect(res.status).toBe(200);
expect(res.body.id).toBeDefined();
expect(res.body.title).toBe('b1');
expect(res.body.author.name).toBe('a1');
expect(res.body.author.email).toBe('e1');
expect(res.body.author.termsAccepted).toBe(false);
expect(res.body.author.books).toHaveLength(1);

id = res.body.id;
});

await request(app.callback())
.get('/book')
.then(res => {
expect(res.status).toBe(200);
expect(res.body[0].id).toBeDefined();
expect(res.body[0].title).toBe('b1');
expect(res.body[0].author.name).toBe('a1');
expect(res.body[0].author.email).toBe('e1');
expect(res.body[0].author.termsAccepted).toBe(false);
});

await request(app.callback())
.put('/book/' + id)
.send({ title: 'b2' })
.then(res => {
expect(res.status).toBe(200);
expect(res.body.id).toBeDefined();
expect(res.body.title).toBe('b2');
expect(res.body.author).toBeDefined();
});
});

});
21 changes: 11 additions & 10 deletions app/controllers/book.controller.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import { QueryOrder, wrap } from '@mikro-orm/sqlite';
import { Context } from 'koa';
import Router from 'koa-router';

import { DI } from '../server';
import { z } from 'zod';

const router = new Router();

router.get('/', async (ctx: Context) => {
router.get('/', async ctx => {
ctx.body = await DI.books.findAll({
populate: ['author'],
orderBy: { title: QueryOrder.DESC },
limit: 20,
});
});

router.get('/:id', async (ctx: Context) => {
router.get('/:id', async ctx => {
try {
const params = z.object({ id: z.number() }).parse(ctx.params);
const params = z.object({ id: z.coerce.number() }).parse(ctx.params);
const book = await DI.books.findOne(params.id, { populate: ['author'] });

if (!book) {
Expand All @@ -31,13 +30,15 @@ router.get('/:id', async (ctx: Context) => {
}
});

router.post('/', async (ctx: Context) => {
if (!ctx.request.body.title || !ctx.request.body.author) {
router.post('/', async ctx => {
const body = (ctx.request as any).body;

if (!body.title || !body.author) {
ctx.throw(400, { message: 'One of `title, author` is missing' });
}

try {
const book = DI.books.create(ctx.request.body);
const book = DI.books.create(body);
await DI.em.flush();

ctx.body = book;
Expand All @@ -47,16 +48,16 @@ router.post('/', async (ctx: Context) => {
}
});

router.put('/:id', async (ctx: Context) => {
router.put('/:id', async ctx => {
try {
const params = z.object({ id: z.number() }).parse(ctx.params);
const params = z.object({ id: z.coerce.number() }).parse(ctx.params);
const book = await DI.books.findOne(params.id);

if (!book) {
return ctx.throw(404, { message: 'Book not found' });
}

wrap(book).assign(ctx.request.body);
wrap(book).assign((ctx.request as any).body);
await DI.em.flush();

ctx.body = book;
Expand Down
10 changes: 5 additions & 5 deletions app/entities/Publisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { Entity, Enum, OneToMany, PrimaryKey, Property } from '@mikro-orm/decora
import { Collection } from '@mikro-orm/sqlite';
import { Book } from '.';

export enum PublisherType {
LOCAL = 'local',
GLOBAL = 'global',
}

@Entity()
export class Publisher {

Expand All @@ -23,8 +28,3 @@ export class Publisher {
}

}

export enum PublisherType {
LOCAL = 'local',
GLOBAL = 'global',
}
10 changes: 6 additions & 4 deletions app/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Koa, { Context } from 'koa';
import http from 'http';
import Koa from 'koa';
import Router from 'koa-router';
import { koaBody } from 'koa-body';
import { EntityManager, EntityRepository, MikroORM, RequestContext } from '@mikro-orm/sqlite';
Expand All @@ -8,6 +9,7 @@ import { Author, Book } from './entities';
import config from './mikro-orm.config';

export const DI = {} as {
server: http.Server;
orm: MikroORM,
em: EntityManager,
authors: EntityRepository<Author>,
Expand All @@ -18,13 +20,13 @@ export const app = new Koa();

// Entry point for all modules.
const api = new Router();
api.get('/', (ctx: Context) => ctx.body = { message: 'Welcome to MikroORM Koa TS example, try CRUD on /author and /book endpoints!' });
api.get('/', ctx => ctx.body = { message: 'Welcome to MikroORM Koa TS example, try CRUD on /author and /book endpoints!' });
api.use('/author', AuthorController.routes());
api.use('/book', BookController.routes());

const port = process.env.PORT || 3000;

(async () => {
export const init = (async () => {
DI.orm = await MikroORM.init(config);
DI.em = DI.orm.em;
DI.authors = DI.orm.em.getRepository(Author);
Expand All @@ -41,7 +43,7 @@ const port = process.env.PORT || 3000;
ctx.body = { message: 'No route found' };
});

app.listen(port, () => {
DI.server = app.listen(port, () => {
console.log(`MikroORM Koa TS example started at http://localhost:${port}`);
});
})();
Loading