Template Engine Integration
Installing a Template Engine
First, you need to install the desired template engine. Here, we use Nunjucks as an example:
npm install nunjucksFor EJS and Pug, use the following commands:
npm install ejs
npm install pugConfiguring the Template Engine
In Koa2, you can use the koa-views middleware to integrate a template engine. koa-views allows you to specify the template engine and provides a render method to render views.
const Koa = require('koa');
const views = require('koa-views');
const path = require('path');
const app = new Koa();
// Configure Nunjucks template engine
app.use(views(path.join(__dirname, 'views'), {
extension: 'njk',
map: { njk: 'nunjucks' }
}));
// For EJS
// app.use(views(path.join(__dirname, 'views'), {
// extension: 'ejs',
// map: { ejs: 'ejs' }
// }));
// For Pug
// app.use(views(path.join(__dirname, 'views'), {
// extension: 'pug',
// map: { pug: 'pug' }
// }));Rendering Views and Passing Data
Once the template engine is configured, you can use the ctx.render method in middleware or route handlers to render views and pass data to the template.
const router = require('koa-router')();
router.get('/', async (ctx) => {
const data = {
title: 'Welcome to My Site',
message: 'Hello, world!'
};
await ctx.render('index.njk', data);
});
app.use(router.routes());
app.use(router.allowedMethods());In the above code, the ctx.render method renders the index.njk file from the views directory and passes the data object to the template.
Template Syntax
Below are basic syntax examples for Nunjucks, EJS, and Pug:
Nunjucks
<!-- views/index.njk -->
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>{{ message }}</h1>
</body>
</html>EJS
<!-- views/index.ejs -->
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= message %></h1>
</body>
</html>Pug
doctype html
html
head
title= title
body
h1= messageError Handling
Middleware for Error Handling
In Koa2, you can use middleware to capture and handle errors. Typically, error-handling middleware should be placed after all other middleware to ensure it captures errors from the entire request-handling chain.
const Koa = require('koa');
const app = new Koa();
// Middleware that may throw an error
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = 'An error occurred.';
}
});
// Another middleware that might throw an error
app.use(async (ctx, next) => {
throw new Error('Something went wrong.');
});In this example, the first middleware captures and handles errors thrown by the second middleware.
Custom Error Pages
You can use a template engine to create custom error pages. When an error occurs, you can render these pages and return them to the client.
const views = require('koa-views');
const path = require('path');
app.use(views(path.join(__dirname, 'views'), {
extension: 'njk',
map: { njk: 'nunjucks' }
}));
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
await ctx.render('error', { message: err.message });
}
});JSONP
Native Koa2 JSONP Implementation
Implementing JSONP
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx) => {
// If the JSONP request is GET
if (ctx.method === 'GET' && ctx.url.split('?')[0] === '/getData.jsonp') {
// Get the JSONP callback
let callbackName = ctx.query.callback || 'callback';
let returnData = {
success: true,
data: {
text: 'this is a jsonp api',
time: new Date().getTime(),
}
};
// JSONP script string
let jsonpStr = `;${callbackName}(${JSON.stringify(returnData)})`;
// Set response type to support cross-origin requests
ctx.type = 'text/javascript';
// Output JSONP string
ctx.body = jsonpStr;
} else {
ctx.body = 'hello jsonp';
}
});
app.listen(3000, () => {
console.log('[demo] jsonp is starting at port 3000');
});Parsing Principle
- JSONP cross-origin output is executable JavaScript code.
- The
ctxoutput type should be'text/javascript'. - The
ctxoutput content is a string of executable JavaScript code containing the returned data. - A callback function name (
callbackName) is required, which the frontend dynamically executes to retrieve the data.
koa-jsonp Middleware
Installation
npm install --save koa-jsonpSimple Example
const Koa = require('koa');
const jsonp = require('koa-jsonp');
const app = new Koa();
// Use middleware
app.use(jsonp());
app.use(async (ctx) => {
let returnData = {
success: true,
data: {
text: 'this is a jsonp api',
time: new Date().getTime(),
}
};
// Directly output JSON
ctx.body = returnData;
});
app.listen(3000, () => {
console.log('[demo] jsonp is starting at port 3000');
});Middleware Chain
Middleware Chain and Execution Order
In Koa2, middleware executes in the order it is added to the application. Each middleware receives a next parameter, and calling this parameter passes control to the next middleware.
app.use(async (ctx, next) => {
console.log('Middleware 1');
await next();
});
app.use(async (ctx, next) => {
console.log('Middleware 2');
await next();
});
app.use(async (ctx) => {
console.log('Middleware 3');
});In this example, the request passes through Middleware 1, Middleware 2, and Middleware 3 in sequence.
Controlling Middleware Execution Flow
You can control the middleware execution flow by calling or not calling next. If next is not called, subsequent middleware will not be executed.
app.use(async (ctx, next) => {
if (ctx.path === '/') {
ctx.body = 'Home page';
} else {
await next();
}
});
app.use(async (ctx) => {
ctx.body = 'Default page';
});In this example, if the request path is '/', only the first middleware is executed; otherwise, both middleware are executed.
Static File Serving
Installing koa-static
First, install the koa-static middleware using npm:
npm install koa-staticConfiguring the Static File Directory
koa-static makes it easy to serve static files from a specific directory. Assume the following directory structure:
project/
|-- server.js
|-- views/
|-- public/
|-- css/
|-- js/
|-- images/Where the public directory contains all static assets. You can configure koa-static in your Koa2 application as follows:
const Koa = require('koa');
const staticFiles = require('koa-static');
const app = new Koa();
// Configure static file directory
// Path to the static assets directory relative to the entry file index.js
const staticPath = './static';
app.use(staticFiles(path.join(__dirname, staticPath)));
app.listen(3000);In this configuration, ./public points to the directory containing static files. You can now access static files like http://localhost:3000/css/style.css, http://localhost:3000/js/app.js, or http://localhost:3000/images/logo.png.
Application Example
Code Directory
├── static # Static assets directory
│ ├── css/
│ ├── image/
│ ├── js/
│ └── index.html
├── util # Utility code
│ ├── content.js # Read request content
│ ├── dir.js # Read directory content
│ ├── file.js # Read file content
│ ├── mimes.js # File type list
│ └── walk.js # Traverse directory content
└── index.js # Entry fileCode Analysis
index.js
const Koa = require('koa');
const path = require('path');
const content = require('./util/content');
const mimes = require('./util/mimes');
const app = new Koa();
// Path to the static assets directory relative to the entry file index.js
const staticPath = './static';
// Parse resource type
function parseMime(url) {
let extName = path.extname(url);
extName = extName ? extName.slice(1) : 'unknown';
return mimes[extName];
}
app.use(async (ctx) => {
// Absolute path to the static assets directory
let fullStaticPath = path.join(__dirname, staticPath);
// Get static resource content, which could be file content, directory, or 404
let _content = await content(ctx, fullStaticPath);
// Parse the type of requested content
let _mime = parseMime(ctx.url);
// Set the context type if a corresponding file type exists
if (_mime) {
ctx.type = _mime;
}
// Output static resource content
if (_mime && _mime.indexOf('image/') >= 0) {
// If it's an image, use Node's native res to output binary data
ctx.res.writeHead(200);
ctx.res.write(_content, 'binary');
ctx.res.end();
} else {
// Otherwise, output text
ctx.body = _content;
}
});
app.listen(3000);
console.log('[demo] static-server is starting at port 3000');util/content.js
const path = require('path');
const fs = require('fs');
// Encapsulated method for reading directory content
const dir = require('./dir');
// Encapsulated method for reading file content
const file = require('./file');
/**
* Get static resource content
* @param {object} ctx - Koa context
* @param {string} fullStaticPath - Absolute path to the static assets directory
* @return {string} - Requested local content
*/
async function content(ctx, fullStaticPath) {
// Construct the absolute path of the requested resource
let reqPath = path.join(fullStaticPath, ctx.url);
// Check if the requested path exists as a directory or file
let exist = fs.existsSync(reqPath);
// Default return content is empty
let content = '';
if (!exist) {
// If the requested path does not exist, return 404
content = '404 Not Found! o(╯□╰)o!';
} else {
// Determine if the path is a directory or file
let stat = fs.statSync(reqPath);
if (stat.isDirectory()) {
// If it's a directory, read directory content
content = dir(ctx.url, reqPath);
} else {
// If it's a file, read file content
content = await file(reqPath);
}
}
return content;
}
module.exports = content;util/dir.js
const url = require('url');
const fs = require('fs');
const path = require('path');
// Method for traversing directory content
const walk = require('./walk');
/**
* Encapsulate directory content
* @param {string} url - URL from the request context, i.e., ctx.url
* @param {string} reqPath - Full local path of the requested static resource
* @return {string} - Directory content encapsulated as HTML
*/
function dir(url, reqPath) {
// Read files and subdirectories in the current directory
let contentList = walk(reqPath);
let html = `<ul>`;
for (let [index, item] of contentList.entries()) {
html = `${html}<li><a href="${url === '/' ? '' : url}/${item}">${item}</a>`;
}
html = `${html}</ul>`;
return html;
}
module.exports = dir;util/file.js
const fs = require('fs');
/**
* Read file method
* @param {string} filePath - Absolute local path of the file
* @return {string|binary}
*/
function file(filePath) {
let content = fs.readFileSync(filePath, 'binary');
return content;
}
module.exports = file;util/walk.js
const fs = require('fs');
const mimes = require('./mimes');
/**
* Traverse and read directory content (subdirectories, filenames)
* @param {string} reqPath - Absolute path of the requested resource
* @return {array} - List of directory contents
*/
function walk(reqPath) {
let files = fs.readdirSync(reqPath);
let dirList = [], fileList = [];
for (let i = 0, len = files.length; i < len; i++) {
let item = files[i];
let itemArr = item.split('.');
let itemMime = (itemArr.length > 1) ? itemArr[itemArr.length - 1] : 'undefined';
if (typeof mimes[itemMime] === 'undefined') {
dirList.push(files[i]);
} else {
fileList.push(files[i]);
}
}
let result = dirList.concat(fileList);
return result;
}
module.exports = walk;util/mimes.js
let mimes = {
'css': 'text/css',
'less': 'text/css',
'gif': 'image/gif',
'html': 'text/html',
'ico': 'image/x-icon',
'jpeg': 'image/jpeg',
'jpg': 'image/jpeg',
'js': 'text/javascript',
'json': 'application/json',
'pdf': 'application/pdf',
'png': 'image/png',
'svg': 'image/svg+xml',
'swf': 'application/x-shockwave-flash',
'tiff': 'image/tiff',
'txt': 'text/plain',
'wav': 'audio/x-wav',
'wma': 'audio/x-ms-wma',
'wmv': 'video/x-ms-wmv',
'xml': 'text/xml'
};
module.exports = mimes;Serving CSS, JavaScript, Images, and Other Static Assets
Once koa-static is configured, Koa2 automatically handles requests for static files. For example, if you reference CSS or JavaScript files in an HTML file:
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<script src="/js/app.js"></script>
</body>
</html>When a user visits http://localhost:3000, Koa2 automatically locates and serves public/css/style.css and public/js/app.js.
Database Integration
MySQL Database Integration
Install Dependencies
Use mysql2 or sequelize (an ORM) as the MySQL driver.
npm install mysql2
# Or use Sequelize
npm install sequelizeUsing mysql
Install the Node.js mysql module:
npm install --save mysqlThe mysql module is a Node.js engine for interacting with MySQL databases, enabling operations like table creation, insertion, deletion, updating, and querying.
Creating a Database Session:
const mysql = require('mysql');
const connection = mysql.createConnection({
host: '127.0.0.1', // Database host
user: 'root', // Database user
password: '123456', // Database password
database: 'my_database' // Selected database
});
// Execute SQL query to read/write to the database
connection.query('SELECT * FROM my_table', (error, results, fields) => {
if (error) throw error;
// connected!
// End the session
connection.release();
});Note: Each event has a start-to-end process, and database sessions must be closed after execution to avoid occupying connection resources.
Creating a Database Connection Pool:
Database operations are often complex and involve multiple sessions. Instead of configuring connection parameters for each session, a connection pool can manage sessions efficiently.
const mysql = require('mysql');
// Create a connection pool
const pool = mysql.createPool({
host: '127.0.0.1', // Database host
user: 'root', // Database user
password: '123456', // Database password
database: 'my_database' // Selected database
});
// Perform session operations in the connection pool
pool.getConnection(function(err, connection) {
connection.query('SELECT * FROM my_table', (error, results, fields) => {
// End the session
connection.release();
// Throw error if any
if (error) throw error;
});
});Using mysql2
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'testdb'
});
app.use(async (ctx) => {
const [rows] = await pool.query('SELECT * FROM users');
ctx.body = rows;
});Using Sequelize
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' */
});
const User = sequelize.define('user', {
firstName: DataTypes.STRING,
lastName: DataTypes.STRING
}, {
// Other model options go here
});
(async () => {
await sequelize.authenticate();
console.log('Connection has been established successfully.');
})();MongoDB Database Integration
Install Dependencies
Use mongoose as the MongoDB ORM.
npm install mongooseUsing Mongoose
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test', { useNewUrlParser: true, useUnifiedTopology: true });
const userSchema = new mongoose.Schema({
name: String,
age: Number
});
const User = mongoose.model('User', userSchema);
app.use(async (ctx) => {
const users = await User.find({});
ctx.body = users;
});PostgreSQL Database Integration
Install Dependencies
Use pg or sequelize (PostgreSQL ORM).
npm install pg
# Or use Sequelize
npm install sequelizeUsing pg
const { Pool } = require('pg');
const pool = new Pool({
user: 'dbuser',
host: 'database.server.com',
database: 'mydatabase',
password: 'secretpassword',
port: 5432,
});
app.use(async (ctx) => {
const res = await pool.query('SELECT * FROM users');
ctx.body = res.rows;
});Sequelize Integration
Sequelize is a Promise-based Node.js ORM that supports multiple databases, including MySQL, PostgreSQL, SQLite, and Microsoft SQL Server.
Install Sequelize
npm install --save sequelize
npm install --save mysql2 # Or install the appropriate driver for your databaseConfigure Sequelize
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' */
});Define Models
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize(/* your database config */);
const User = sequelize.define('User', {
username: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
unique: true
}
}, {
timestamps: true,
underscored: true
});Perform Database Operations
app.use(async (ctx) => {
const user = await User.create({
username: 'john_doe',
email: 'john@example.com'
});
const foundUser = await User.findOne({
where: {
email: 'john@example.com'
}
});
ctx.body = foundUser;
});TypeORM Integration
TypeORM is an ORM that supports TypeScript and JavaScript and works with most mainstream relational databases.
Install TypeORM
npm install typeorm reflect-metadata mysql2 # Or install the appropriate driver for your databaseConfigure TypeORM
import { createConnection } from 'typeorm';
createConnection({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'user',
password: 'password',
database: 'mydb',
entities: [
'dist/entities/*.js'
],
synchronize: true,
});Define Entities
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
email: string;
}Perform Database Operations
import { getRepository } from 'typeorm';
app.use(async (ctx) => {
const userRepository = getRepository(User);
const user = userRepository.create({
username: 'john_doe',
email: 'john@example.com'
});
await userRepository.save(user);
const foundUser = await userRepository.findOne({
where: { email: 'john@example.com' }
});
ctx.body = foundUser;
});Prisma Integration
Install Prisma CLI and Prisma Client
First, install the Prisma CLI globally:
npm install -g prismaThen, install the Prisma Client in your project:
npx prisma generateThis generates the Prisma Client, the primary way to interact with the database.
Configure Prisma Schema
Create a prisma/schema.prisma file in your project root to define your database models:
datasource db {
provider = 'postgresql'
url = env('DATABASE_URL')
}
generator client {
provider = 'prisma-client-js'
}
model User {
id Int @id @default(autoincrement())
email String @unique
createdAt DateTime @default(now())
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}Generate Prisma Client
Run the following command to generate the Prisma Client:
npx prisma generateThis generates the Prisma Client in the node_modules/.prisma/client directory.
Using Prisma in Koa2
In your Koa2 application, import the Prisma Client and use it for database operations:
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
const app = new Koa();
app.use(async (ctx) => {
const user = await prisma.user.findUnique({
where: Juvenile@id: john.doe@example.com' }
});
ctx.body = user;
});Database Migrations
Prisma provides a database migration tool to safely modify the database schema:
npx prisma migrate dev --name initThis creates a new migration file and applies it to your database.
Exception Handling and Connection Cleanup
In production, properly handle Prisma exceptions and gracefully close the Prisma Client connection when the application shuts down:
process.on('SIGTERM', async () => {
await prisma.$disconnect();
process.exit(0);
});File Upload
Creating the Basic Application Structure
app.js: Main application file.routes/upload.js: Handles file uploads.
// app.js
const Koa = require('koa');
const router = require('koa-router')();
const bodyParser = require('koa-bodyparser');
const uploadRouter = require('./routes/upload');
const app = new Koa();
// Use middleware
app.use(bodyParser());
app.use(uploadRouter.routes()).use(uploadRouter.allowedMethods());
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});File Upload Route
Use multer to handle file uploads. Set the storage path and file type restrictions.
// routes/upload.js
const Router = require('koa-router');
const multer = require('multer');
const path = require('path');
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/');
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname));
}
});
const upload = multer({ storage: storage });
const router = new Router();
router.post('/upload', upload.single('file'), async (ctx) => {
ctx.body = {
success: true,
message: 'File uploaded successfully',
fileName: ctx.req.file.filename
};
});
module.exports = router;Testing File Upload Functionality
Use Postman or curl to send a POST request. Configure the request URL and file field name.
curl -X POST -F "file=@/path/to/file.jpg" http://localhost:3000/uploadError Handling and Validation
Error Handling:
- File size restrictions.
- File type restrictions.
Validation:
- Ensure files are saved correctly.
- Return appropriate response information.
// routes/upload.js
const upload = multer({
storage: storage,
limits: { fileSize: 1024 * 1024 * 5 }, // 5MB
fileFilter: (req, file, cb) => {
if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)) {
return cb(new Error('Only image files are allowed!'));
}
cb(null, true);
}
});
router.post('/upload', upload.single('file'), async (ctx) => {
try {
ctx.body = {
success: true,
message: 'File uploaded successfully',
fileName: ctx.req.file.filename
};
} catch (err) {
ctx.status = 400;
ctx.body = { error: err.message };
}
});Cookies and Sessions
Installing Required Packages
To use Cookies and Sessions, install the koa-session package, the official session middleware for Koa.
npm install koa-session --saveConfiguring Sessions
To configure sessions in Koa2, create a session configuration object and pass it to the koa-session middleware.
// app.js
const Koa = require('koa');
const session = require('koa-session');
const app = new Koa();
// Configure Session
const CONFIG = {
key: 'koa:sess', // Cookie key (default is koa:sess)
maxAge: 86400000, // Cookie expiration time in ms (default is 1 day)
autoCommit: true, // Auto-commit headers (default: true)
overwrite: true, // Allow overwriting (default: true)
httpOnly: true, // Cookie accessible only by the server (default: true)
signed: false, // Signature default (default: true)
rolling: false, // Force-set cookie on every request to reset expiration (default: false)
renew: false, // Renew session when about to expire (default: false)
};
app.keys = ['some secret hurr']; // Key for signing
app.use(session(CONFIG, app));Using Sessions
Once sessions are configured, you can use them in middleware or route handlers to store and retrieve data.
// app.js
app.use(async (ctx) => {
if (ctx.request.method === 'POST') {
// Set session
ctx.session.username = ctx.request.body.username;
ctx.body = { message: 'Logged in!' };
} else {
// Get session
const username = ctx.session.username;
ctx.body = { username };
}
});Using Cookies
Cookies can be accessed directly via ctx.cookies. Use ctx.cookies.get(name[, options]) to retrieve a cookie and ctx.cookies.set(name, value[, options]) to set a cookie.
// Set Cookie
ctx.cookies.set('username', 'john_doe', { maxAge: 600000 });
ctx.cookies.set(
'cid',
'hello world',
{
domain: 'localhost', // Domain for the cookie
path: '/index', // Path for the cookie
maxAge: 10 * 60 * 1000, // Cookie duration
expires: new Date('2017-02-15'), // Cookie expiration date
httpOnly: false, // Accessible only via HTTP
overwrite: false // Allow overwriting
}
);
ctx.body = 'cookie is ok';
// Get Cookie
const username = ctx.cookies.get('username');Example: Login Authentication
Below is a simple login example using sessions to track a user’s login status.
// app.js
const Koa = require('koa');
const session = require('koa-session');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
// Configure Session
const CONFIG = {
key: 'koa:sess',
maxAge: 86400000,
autoCommit: true,
overwrite: true,
httpOnly: true,
signed: false,
rolling: false,
renew: false
};
app.keys = ['some secret hurr'];
app.use(session(CONFIG, app));
// Login route
router.post('/login', async (ctx) => {
const { username, password } = ctx.request.body;
// Simple user validation logic
if (username === 'admin' && password === '123456') {
ctx.session.user = { id: 1, username };
ctx.body = { message: 'Login successful' };
} else {
ctx.status = 401;
ctx.body = { message: 'Invalid credentials' };
}
});
// Protected route
router.get('/protected', async (ctx) => {
const user = ctx.session.user;
if (user) {
ctx.body = { message: `Welcome, ${user.username}!` };
} else {
ctx.status = 401;
ctx.body = { message: 'You need to login first' };
}
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});Example 2: MySQL Session Storage
const Koa = require('koa');
const session = require('koa-session-minimal');
const MysqlSession = require('koa-mysql-session');
const app = new Koa();
// Configure MySQL for session storage
let store = new MysqlSession({
user: 'root',
password: 'abc123',
database: 'koa_demo',
host: '127.0.0.1',
});
// Cookie configuration for storing session ID
let cookie = {
maxAge: '', // Cookie duration
expires: '', // Cookie expiration date
path: '', // Path for the cookie
domain: '', // Domain for the cookie
httpOnly: '', // Accessible only via HTTP
overwrite: '', // Allow overwriting
secure: '',
sameSite: '',
signed: '',
};
// Use session middleware
app.use(session({
key: 'SESSION_ID',
store: store,
cookie: cookie
}));
app.use(async (ctx) => {
// Set session
if (ctx.url === '/set') {
ctx.session = {
user_id: Math.random().toString(36).substr(2),
count: 0
};
ctx.body = ctx.session;
} else if (ctx.url === '/') {
// Read session information
ctx.session.count = ctx.session.count + 1;
ctx.body = ctx.session;
}
});
app.listen(3000, () => {
console.log('[demo] session is starting at port 3000');
});Custom Middleware
Installing Koa
Ensure Koa2 is installed:
npm install koa --saveCreating a Basic Application
Create a basic Koa2 application:
// app.js
const Koa = require('koa');
const app = new Koa();
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});Writing Middleware
Middleware is a function that takes the context object ctx and a next function as parameters. Middleware can perform any operation and call next() to pass control to the next middleware.
Example: Logging Middleware
Below is a simple logging middleware that records the timestamp and method of requests.
function loggerMiddleware() {
return async (ctx, next) => {
const start = Date.now();
await next();
const end = Date.now();
console.log(`${ctx.method} ${ctx.url} - ${end - start}ms`);
};
}
app.use(loggerMiddleware());Example: Error Handling Middleware
An error-handling middleware to catch unhandled errors and return appropriate HTTP status codes and error messages.
function errorHandlerMiddleware() {
return async (ctx, next) => {
try {
await next();
} catch (err) {
console.error(err);
ctx.status = err.status || 500;
ctx.body = {
message: err.message,
error: err
};
}
};
}
app.use(errorHandlerMiddleware());Example: Authentication Middleware
A simple authentication middleware to check if a user has permission to access a specific resource.
function authMiddleware() {
return async (ctx, next) => {
const requiredRole = ctx.params.role || 'user';
const userRole = ctx.state.user?.role || 'guest';
if (userRole !== requiredRole) {
ctx.status = 403;
ctx.body = {
message: 'Forbidden'
};
return;
}
await next();
};
}
app.use(authMiddleware());Example: JSON API Response Middleware
A middleware to automatically convert JSON objects into HTTP response bodies.
function jsonApiResponseMiddleware() {
return async (ctx, next) => {
await next();
if (ctx.body && typeof ctx.body === 'object' && !Buffer.isBuffer(ctx.body)) {
ctx.body = JSON.stringify(ctx.body);
ctx.type = 'application/json';
}
};
}
app.use(jsonApiResponseMiddleware());Using Middleware
Incorporate these middleware into the application:
// app.js
const Koa = require('koa');
const app = new Koa();
// Logging middleware
app.use(loggerMiddleware());
// Error handling middleware
app.use(errorHandlerMiddleware());
// JSON API response middleware
app.use(jsonApiResponseMiddleware());
// Authentication middleware
app.use(authMiddleware());
// Route handling
app.use(async (ctx) => {
ctx.body = {
message: 'Hello, World!'
};
});
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});



