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-routerBasic 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