Koa Integration

Learn how to integrate Qryn with Koa for modern Node.js applications.

Installation

Install Qryn and the Koa adapter for seamless integration.

Install Dependencies

npm install qryn koa koa-router
npm install -D @types/koa @types/koa-router

Basic Setup

import Koa from 'koa';
import Router from 'koa-router';
import { Qryn } from 'qryn';
import { createKoaAdapter } from 'qryn/adapters/koa';

const app = new Koa();
const router = new Router();

const qryn = new Qryn({
  database: {
    type: 'sqlite',
    connection: './database.sqlite'
  }
});

// Create Koa adapter
const qrynAdapter = createKoaAdapter(qryn);

// Mount Qryn API
router.use('/api/qryn', qrynAdapter.routes(), qrynAdapter.allowedMethods());

app.use(router.routes());
app.use(router.allowedMethods());

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

Configuration

Configure Qryn with Koa for optimal performance.

Koa Configuration

Advanced Koa integration options

Middleware Configuration

import Koa from 'koa';
import Router from 'koa-router';
import bodyParser from 'koa-bodyparser';
import cors from '@koa/cors';
import { Qryn } from 'qryn';
import { createKoaAdapter } from 'qryn/adapters/koa';

const app = new Koa();
const router = new Router();

// Global middleware
app.use(cors({
  origin: '*',
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowHeaders: ['Content-Type', 'Authorization']
}));

app.use(bodyParser({
  jsonLimit: '1mb',
  textLimit: '1mb',
  formLimit: '1mb'
}));

// Logging middleware
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

// Error handling middleware
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.body = {
      error: err.message || 'Internal Server Error'
    };
    ctx.app.emit('error', err, ctx);
  }
});

const qryn = new Qryn({
  database: {
    type: 'postgresql',
    connection: process.env.DATABASE_URL
  }
});

// Create Koa adapter with custom options
const qrynAdapter = createKoaAdapter(qryn, {
  prefix: '/api/qryn',
  middleware: {
    before: [
      // Custom middleware before Qryn
      async (ctx, next) => {
        console.log('Before Qryn:', ctx.method, ctx.path);
        await next();
      }
    ],
    after: [
      // Custom middleware after Qryn
      async (ctx, next) => {
        console.log('After Qryn:', ctx.status);
        await next();
      }
    ]
  }
});

router.use('/api/qryn', qrynAdapter.routes(), qrynAdapter.allowedMethods());

Error Handling

// Custom error handling
app.on('error', (err, ctx) => {
  console.error('Server error:', err);
});

// Error handling middleware
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    
    if (err.name === 'ValidationError') {
      ctx.body = {
        error: 'Validation Error',
        details: err.details
      };
      return;
    }
    
    if (err.name === 'NotFoundError') {
      ctx.body = {
        error: 'Not Found',
        message: err.message
      };
      return;
    }
    
    ctx.body = {
      error: 'Internal Server Error',
      message: 'Something went wrong'
    };
  }
});

// 404 handler
app.use(async (ctx, next) => {
  await next();
  if (ctx.status === 404) {
    ctx.body = {
      error: 'Not Found',
      message: 'Route not found'
    };
  }
});

Custom Routes

Add custom routes alongside Qryn's auto-generated API.

Custom Route Example

// Health check route
router.get('/health', async (ctx) => {
  ctx.body = {
    status: 'ok',
    timestamp: new Date().toISOString()
  };
});

// Stats route
router.get('/stats', async (ctx) => {
  try {
    const stats = await qryn.query({
      model: 'User',
      select: ['id'],
      limit: 1
    });
    
    ctx.body = {
      totalUsers: stats.meta.total,
      timestamp: new Date().toISOString()
    };
  } catch (error) {
    ctx.status = 500;
    ctx.body = { error: 'Failed to get stats' };
  }
});

// Authentication route
router.post('/auth/login', async (ctx) => {
  try {
    const { email, password } = ctx.request.body;
    
    if (!email || !password) {
      ctx.status = 400;
      ctx.body = { error: 'Email and password are required' };
      return;
    }
    
    const user = await qryn.query({
      model: 'User',
      where: { email, password }
    });
    
    if (user.data.length === 0) {
      ctx.status = 401;
      ctx.body = { error: 'Invalid credentials' };
      return;
    }
    
    const token = generateJWT(user.data[0]);
    
    ctx.body = { token, user: user.data[0] };
  } catch (error) {
    ctx.status = 500;
    ctx.body = { error: 'Login failed' };
  }
});

// Protected route
router.get('/protected', async (ctx, next) => {
  const token = ctx.headers.authorization;
  
  if (!token) {
    ctx.status = 401;
    ctx.body = { error: 'Authentication required' };
    return;
  }
  
  try {
    const user = verifyJWT(token);
    ctx.state.user = user;
    await next();
  } catch (error) {
    ctx.status = 403;
    ctx.body = { error: 'Invalid token' };
    return;
  }
}, async (ctx) => {
  ctx.body = {
    message: 'Protected resource',
    user: ctx.state.user
  };
});

Route Middleware

// Authentication middleware
const authenticate = async (ctx, next) => {
  const token = ctx.headers.authorization;
  
  if (!token) {
    ctx.status = 401;
    ctx.body = { error: 'Authentication required' };
    return;
  }
  
  try {
    const user = verifyJWT(token);
    ctx.state.user = user;
    await next();
  } catch (error) {
    ctx.status = 403;
    ctx.body = { error: 'Invalid token' };
    return;
  }
};

// Role-based authorization middleware
const authorize = (roles) => {
  return async (ctx, next) => {
    if (!ctx.state.user) {
      ctx.status = 401;
      ctx.body = { error: 'Authentication required' };
      return;
    }
    
    if (!roles.includes(ctx.state.user.role)) {
      ctx.status = 403;
      ctx.body = { error: 'Insufficient permissions' };
      return;
    }
    
    await next();
  };
};

// Apply middleware to routes
router.get('/api/admin/users', authenticate, authorize(['admin']), async (ctx) => {
  const users = await qryn.query({
    model: 'User',
    limit: 100
  });
  
  ctx.body = users;
});

router.get('/api/users/:id', authenticate, authorize(['user', 'admin']), async (ctx) => {
  const { id } = ctx.params;
  
  // Users can only access their own data
  if (ctx.state.user.role === 'user' && ctx.state.user.userId !== id) {
    ctx.status = 403;
    ctx.body = { error: 'Access denied' };
    return;
  }
  
  const user = await qryn.query({
    model: 'User',
    where: { id }
  });
  
  ctx.body = user.data[0];
});

Authentication

Implement authentication with Koa and Qryn.

JWT Authentication

import jwt from 'jsonwebtoken';

// JWT utilities
const generateJWT = (user) => {
  return jwt.sign(
    { userId: user.id, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '24h' }
  );
};

const verifyJWT = (token) => {
  return jwt.verify(token, process.env.JWT_SECRET);
};

// Authentication middleware
const authenticate = async (ctx, next) => {
  const authHeader = ctx.headers.authorization;
  const token = authHeader && authHeader.split(' ')[1];
  
  if (!token) {
    ctx.status = 401;
    ctx.body = { error: 'Access token required' };
    return;
  }
  
  try {
    const decoded = verifyJWT(token);
    ctx.state.user = decoded;
    await next();
  } catch (error) {
    ctx.status = 403;
    ctx.body = { error: 'Invalid token' };
    return;
  }
};

// Login route
router.post('/auth/login', async (ctx) => {
  try {
    const { email, password } = ctx.request.body;
    
    const user = await qryn.query({
      model: 'User',
      where: { email, password }
    });
    
    if (user.data.length === 0) {
      ctx.status = 401;
      ctx.body = { error: 'Invalid credentials' };
      return;
    }
    
    const token = generateJWT(user.data[0]);
    
    ctx.body = { token, user: user.data[0] };
  } catch (error) {
    ctx.status = 500;
    ctx.body = { error: 'Login failed' };
  }
});

// Protected route
router.get('/api/protected', authenticate, async (ctx) => {
  ctx.body = {
    message: 'Protected resource',
    user: ctx.state.user
  };
});

Session Authentication

import session from 'koa-session';

// Session configuration
app.keys = [process.env.SESSION_SECRET];

app.use(session({
  key: 'qryn:sess',
  maxAge: 86400000, // 24 hours
  overwrite: true,
  httpOnly: true,
  signed: true,
  rolling: false,
  renew: false
}, app));

// Session authentication middleware
const requireAuth = async (ctx, next) => {
  if (!ctx.session.userId) {
    ctx.status = 401;
    ctx.body = { error: 'Authentication required' };
    return;
  }
  await next();
};

// Login endpoint
router.post('/auth/login', async (ctx) => {
  try {
    const { email, password } = ctx.request.body;
    
    const user = await qryn.query({
      model: 'User',
      where: { email, password }
    });
    
    if (user.data.length === 0) {
      ctx.status = 401;
      ctx.body = { error: 'Invalid credentials' };
      return;
    }
    
    ctx.session.userId = user.data[0].id;
    ctx.body = { message: 'Login successful' };
  } catch (error) {
    ctx.status = 500;
    ctx.body = { error: 'Login failed' };
  }
});

// Logout endpoint
router.post('/auth/logout', async (ctx) => {
  ctx.session = null;
  ctx.body = { message: 'Logout successful' };
});

// Apply authentication to Qryn routes
router.use('/api/qryn', requireAuth);

Production Setup

Configure Koa with Qryn for production deployment.

Production Configuration

import Koa from 'koa';
import Router from 'koa-router';
import bodyParser from 'koa-bodyparser';
import cors from '@koa/cors';
import helmet from 'koa-helmet';
import compress from 'koa-compress';
import { Qryn } from 'qryn';
import { createKoaAdapter } from 'qryn/adapters/koa';

const app = new Koa();
const router = new Router();

// Security middleware
app.use(helmet());

// Compression middleware
app.use(compress({
  threshold: 1024,
  gzip: {
    flush: require('zlib').constants.Z_SYNC_FLUSH
  },
  deflate: {
    flush: require('zlib').constants.Z_SYNC_FLUSH
  }
}));

// CORS middleware
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(','),
  credentials: true
}));

// Body parser middleware
app.use(bodyParser({
  jsonLimit: '1mb',
  textLimit: '1mb',
  formLimit: '1mb'
}));

// Logging middleware
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

// Error handling middleware
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.body = {
      error: err.message || 'Internal Server Error'
    };
    ctx.app.emit('error', err, ctx);
  }
});

// Qryn configuration for production
const qryn = new Qryn({
  database: {
    type: 'postgresql',
    connection: process.env.DATABASE_URL,
    pool: {
      min: 5,
      max: 50,
      idleTimeoutMillis: 30000
    }
  },
  cache: {
    type: 'redis',
    connection: process.env.REDIS_URL
  },
  security: {
    rateLimit: {
      enabled: true,
      windowMs: 900000,
      max: 1000
    }
  }
});

// Register Qryn
const qrynAdapter = createKoaAdapter(qryn);
router.use('/api/qryn', qrynAdapter.routes(), qrynAdapter.allowedMethods());

// Health check endpoint
router.get('/health', async (ctx) => {
  ctx.body = {
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime()
  };
});

app.use(router.routes());
app.use(router.allowedMethods());

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Docker Configuration

# Dockerfile
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 3000

CMD ["node", "server.js"]

Best Practices

Do

  • • Use middleware for common functionality
  • • Implement proper error handling
  • • Use environment variables
  • • Enable compression and security
  • • Implement rate limiting
  • • Use connection pooling
  • • Monitor performance

Don't

  • • Skip error handling
  • • Hardcode configuration
  • • Ignore security middleware
  • • Skip rate limiting
  • • Use inefficient queries
  • • Skip monitoring
  • • Ignore performance optimization