Backend Development 16 min read

NodeJS and Egg Framework Practices for Frontend‑Backend Integration and Service Development

This article details the adoption of NodeJS and the Egg framework for front‑end/back‑end separation, covering SSR considerations, directory planning, scheduled tasks, error handling, service design, custom logging, Elasticsearch integration, and Docker deployment, offering practical insights and best‑practice recommendations for backend development.

HomeTech
HomeTech
HomeTech
NodeJS and Egg Framework Practices for Frontend‑Backend Integration and Service Development

The article begins by explaining the motivation for using NodeJS in modern frontend projects, especially for SSR (Server‑Side Rendering) scenarios, and how the team decided to adopt the Egg framework as a robust backend solution.

Background & Selection – The team evaluated Express, Koa, and Egg, ultimately choosing Egg for its convention‑over‑configuration philosophy and community support.

Workflow – A custom CLI (CAA) was created to generate static pages and API proxies, enabling parallel development of frontend and backend components.

Directory Planning – The project structure emphasizes clear controller and service layers, with business‑specific subdirectories such as service/club , service/uc , and service/qa . Example layout:

app
  ├── controller
  │   ├── pages
  │   └── api
  ├── service
  │   ├── club
  │   ├── uc
  │   └── qa
  └── ...

Scheduled Tasks – A periodic job updates shared navigation headers and footers. Key code:

class UpdateCommonView extends Subscription {
  static get schedule() {
    return { interval: '10m', type: 'worker' };
  }
  async subscribe() {
    const headerViewDir = path.join(__dirname, '../view/layouts/header');
    const footerViewDir = path.join(__dirname, '../view/layouts/footer');
    // ensure directories exist
    if (!fs.existsSync(headerViewDir)) {
      try { fs.mkdirSync(headerViewDir, { recursive: true }); } catch (e) { this.logger.error(e); }
    }
    if (!fs.existsSync(footerViewDir)) {
      try { fs.mkdirSync(footerViewDir, { recursive: true }); } catch (e) { this.logger.error(e); }
    }
    // fetch and write header/footer files
    const res = await this.ctx.curl(`${feAPIUrl}/topbar/club`);
    if (res.data) {
      this._writeFile(path.join(headerViewDir, 'dark.html'), res.data);
      this._writeFile(path.join(footerViewDir, 'dark.html'), res.data);
    } else {
      this.logger.error(new Error('===更新黑色公共头失败==='));
    }
  }
  _writeFile(file, data) {
    try { fs.writeFileSync(file, data); } catch (e) { this.logger.error(e); }
  }
}
module.exports = UpdateCommonView;

Service Layer – Services inherit from a base class that centralizes API result validation. Example:

class BaseService extends Service {
  checkApiResult(result) {
    if (result.status !== 200) {
      const resultMsg = result.data && result.data.message;
      const errorMsg = resultMsg ? resultMsg : 'unknown origin server error';
      this.ctx.throw(result.status, errorMsg);
    }
  }
}
module.exports = BaseService;

Error Handling Middleware – A global middleware captures async errors and emits an "error" event:

module.exports = () => {
  return async function errorHandler(ctx, next) {
    try {
      await next();
    } catch (e) {
      ctx.app.emit('error', e, ctx);
    }
  };
};

Custom Logging – To meet internal log‑format requirements, a ContextLogger class formats messages with request context:

class ContextLogger {
  constructor(ctx, logger) { this.ctx = ctx; this._logger = logger; }
  contextFormatter(meta) {
    return `${meta.date} ${meta.level} ${meta.pid} ${meta.message}`;
  }
}
['error','warn','info','debug'].forEach(level => {
  const LEVEL = level.toUpperCase();
  ContextLogger.prototype[level] = function() {
    const meta = { ctx: this.ctx, formatter: this._logger.options[`${level}Formatter`] || this.contextFormatter };
    this._logger.log(LEVEL, arguments, meta);
  };
});
module.exports = { ContextLogger };

Elasticsearch Integration – Errors are also pushed to an ES cluster for analysis:

module.exports = app => {
  const esClient = new elasticsearch.Client({ host: app.config.esURI });
  app.on('error', async (e, ctx) => {
    const logBody = {
      '@timestamp': new Date(),
      level: 'ERROR',
      message: e.message,
      exception: util.format(e.stack),
      processid: process.pid,
      clientip: ctx.ip,
      url: ctx.href,
      method: ctx.method,
      query: util.format(ctx.query),
      reqbody: util.format(ctx.request.body),
      ua: ctx.header['user-agent'],
      datetime: moment().format('YYYY-MM-DD HH:mm:ss SSS')
    };
    await esClient.create({
      index: `nodejs_err_${moment().format('YYYY.MM.DD')}`,
      type: 'table',
      id: shortid.generate(),
      body: logBody
    });
  });
};

Docker Deployment – The article notes removing the "--daemon" flag from the npm start script and using host 0.0.0.0 instead of 127.0.0.1 to avoid 502 errors.

In conclusion, the author reflects on the practical use of NodeJS core modules like path and fs , emphasizing that backend development is approachable for frontend engineers when they adopt incremental best practices.

Dockerbackend developmentSSRLoggingnodejsEgg
HomeTech
Written by

HomeTech

HomeTech tech sharing

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.