阿里云主机折上折
  • 微信号
Current Site:Index > Response handling for multiple content types

Response handling for multiple content types

Author:Chuan Chen 阅读数:6842人阅读 分类: Node.js

Handling JSON Responses

In Koa2, handling JSON responses is one of the most common scenarios. By directly returning a JavaScript object via ctx.body, Koa automatically converts it to JSON format and sets the correct Content-Type header.

router.get('/api/user', async (ctx) => {
  ctx.body = {
    id: 1,
    name: '张三',
    age: 28,
    hobbies: ['编程', '阅读', '旅行']
  };
});

When a client requests this route, they will receive the following response:

{
  "id": 1,
  "name": "张三",
  "age": 28,
  "hobbies": ["编程", "阅读", "旅行"]
}

For more complex scenarios, you can use the koa-json middleware to customize the JSON format:

const json = require('koa-json');

app.use(json({
  pretty: process.env.NODE_ENV !== 'production',
  param: 'pretty',
  spaces: 2
}));

Handling HTML Responses

When returning HTML content, you need to manually set the Content-Type to text/html. You can use template engines like EJS or Pug, or directly return an HTML string.

router.get('/welcome', async (ctx) => {
  ctx.type = 'text/html';
  ctx.body = `
    <!DOCTYPE html>
    <html>
      <head>
        <title>Welcome Page</title>
      </head>
      <body>
        <h1>Welcome to Our Website</h1>
        <p>Current Time: ${new Date().toLocaleString()}</p>
      </body>
    </html>
  `;
});

Example using the EJS template engine:

const views = require('koa-views');

app.use(views(__dirname + '/views', {
  extension: 'ejs'
}));

router.get('/profile', async (ctx) => {
  await ctx.render('profile', {
    user: {
      name: '李四',
      avatar: '/images/avatar.jpg'
    }
  });
});

Handling File Downloads

For file download responses, you need to set the appropriate Content-Disposition header. Koa provides a convenient attachment method.

const fs = require('fs');
const path = require('path');

router.get('/download', async (ctx) => {
  const filePath = path.join(__dirname, 'files/report.pdf');
  ctx.attachment('Monthly Report.pdf');
  ctx.type = 'application/pdf';
  ctx.body = fs.createReadStream(filePath);
});

To dynamically generate and download a file:

router.get('/export-csv', async (ctx) => {
  const data = [
    ['Name', 'Age', 'City'],
    ['张三', '28', '北京'],
    ['李四', '32', '上海']
  ];
  
  const csvContent = data.map(row => row.join(',')).join('\n');
  
  ctx.attachment('User Data.csv');
  ctx.type = 'text/csv';
  ctx.body = csvContent;
});

Handling Streaming Responses

Koa natively supports streaming responses, which is useful for large files or real-time data.

const { PassThrough } = require('stream');

router.get('/stream', async (ctx) => {
  ctx.type = 'text/plain';
  const stream = new PassThrough();
  
  let count = 0;
  const timer = setInterval(() => {
    stream.write(`Data Chunk ${count++}\n`);
    if (count >= 10) {
      clearInterval(timer);
      stream.end();
    }
  }, 500);
  
  ctx.body = stream;
});

Another practical example is proxy forwarding:

const axios = require('axios');

router.get('/proxy-image', async (ctx) => {
  const response = await axios.get('https://example.com/large-image.jpg', {
    responseType: 'stream'
  });
  
  ctx.type = response.headers['content-type'];
  ctx.body = response.data;
});

Handling SSE (Server-Sent Events)

SSE allows the server to push events to the client, suitable for real-time updates.

router.get('/sse', async (ctx) => {
  ctx.request.socket.setTimeout(0);
  ctx.req.socket.setNoDelay(true);
  ctx.req.socket.setKeepAlive(true);
  
  ctx.set({
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });
  
  const stream = new PassThrough();
  ctx.body = stream;
  
  let count = 0;
  const sendEvent = () => {
    stream.write(`event: update\n`);
    stream.write(`id: ${Date.now()}\n`);
    stream.write(`data: ${JSON.stringify({ count: count++, time: new Date() })}\n\n`);
  };
  
  const timer = setInterval(sendEvent, 1000);
  
  ctx.req.on('close', () => {
    clearInterval(timer);
    stream.end();
  });
});

Handling GraphQL Responses

Integrating GraphQL services in Koa requires additional middleware.

const { graphqlHTTP } = require('koa-graphql');
const { buildSchema } = require('graphql');

const schema = buildSchema(`
  type Query {
    hello: String
    user(id: ID!): User
  }
  
  type User {
    id: ID!
    name: String
    email: String
  }
`);

const rootValue = {
  hello: () => 'Hello world!',
  user: ({ id }) => ({
    id,
    name: 'User' + id,
    email: `user${id}@example.com`
  })
};

router.all('/graphql', graphqlHTTP({
  schema,
  rootValue,
  graphiql: true
}));

Handling WebSocket Upgrades

Although Koa itself does not directly handle WebSocket, it can be implemented with other libraries.

const Koa = require('koa');
const WebSocket = require('ws');

const app = new Koa();
const server = app.listen(3000);

const wss = new WebSocket.Server({ server });

wss.on('connection', (ws) => {
  ws.on('message', (message) => {
    console.log('Received message:', message);
    ws.send(`Server reply: ${message}`);
  });
  
  ws.send('Connection established');
});

// Koa middleware
app.use(async (ctx) => {
  // Regular HTTP request handling
  ctx.body = 'HTTP request handled';
});

Handling Content Negotiation

Return different response formats based on the client's Accept header.

router.get('/resource', async (ctx) => {
  const data = {
    id: 123,
    title: 'Content Negotiation Example',
    content: 'This is an example demonstrating different response formats'
  };
  
  switch (ctx.accepts('json', 'html', 'xml', 'text')) {
    case 'json':
      ctx.body = data;
      break;
    case 'html':
      ctx.type = 'text/html';
      ctx.body = `
        <!DOCTYPE html>
        <html>
          <head><title>${data.title}</title></head>
          <body>
            <h1>${data.title}</h1>
            <p>${data.content}</p>
          </body>
        </html>
      `;
      break;
    case 'xml':
      ctx.type = 'application/xml';
      ctx.body = `
        <?xml version="1.0" encoding="UTF-8"?>
        <resource>
          <id>${data.id}</id>
          <title>${data.title}</title>
          <content>${data.content}</content>
        </resource>
      `;
      break;
    default:
      ctx.type = 'text/plain';
      ctx.body = `${data.title}\n\n${data.content}`;
  }
});

Handling Error Responses

A unified error-handling middleware can standardize error response formats.

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.type = 'application/json';
    ctx.body = {
      error: {
        code: err.code || 'INTERNAL_ERROR',
        message: err.message,
        details: err.details,
        timestamp: new Date().toISOString()
      }
    };
    
    if (process.env.NODE_ENV === 'development') {
      ctx.body.error.stack = err.stack;
    }
    
    ctx.app.emit('error', err, ctx);
  }
});

// Usage example
router.get('/protected', async (ctx) => {
  if (!ctx.headers.authorization) {
    const error = new Error('Unauthorized access');
    error.status = 401;
    error.code = 'UNAUTHORIZED';
    throw error;
  }
  
  ctx.body = { message: 'Welcome to protected resource' };
});

Handling Redirect Responses

Koa provides convenient methods for redirection.

router.get('/old-route', async (ctx) => {
  ctx.redirect('/new-route');
  ctx.status = 301; // Permanent redirect
});

router.get('/login', async (ctx) => {
  if (!ctx.session.user) {
    ctx.redirect('/auth?return_url=' + encodeURIComponent(ctx.url));
    return;
  }
  
  ctx.body = 'Welcome back';
});

// Redirect with flash messages
router.post('/comments', async (ctx) => {
  try {
    await createComment(ctx.request.body);
    ctx.flash = { type: 'success', message: 'Comment published successfully' };
    ctx.redirect('back');
  } catch (err) {
    ctx.flash = { type: 'error', message: err.message };
    ctx.redirect('back');
  }
});

Handling Custom Content Types

For non-standard content types, you can manually set the Content-Type.

router.get('/ical', async (ctx) => {
  const icalContent = `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//CalDAV Client//EN
BEGIN:VEVENT
UID:12345
DTSTAMP:20230501T120000Z
DTSTART:20230501T130000Z
DTEND:20230501T140000Z
SUMMARY:Team Meeting
END:VEVENT
END:VCALENDAR`;
  
  ctx.type = 'text/calendar';
  ctx.body = icalContent;
});

router.get('/rss', async (ctx) => {
  const rssContent = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Example RSS</title>
    <description>This is an example RSS feed</description>
    <item>
      <title>First News</title>
      <description>This is the content of the first news</description>
    </item>
  </channel>
</rss>`;
  
  ctx.type = 'application/rss+xml';
  ctx.body = rssContent;
});

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.