Node.jsバックエンド面接質問:完全ガイド2026

Node.jsバックエンド面接で最も頻出の25問。Event Loop、async/await、Streams、クラスタリング、パフォーマンスを詳細な回答で解説します。

Node.jsバックエンド面接質問 - 完全ガイド

Node.jsバックエンド面接では、ランタイム内部の理解、非同期パターンの習熟度、高パフォーマンスアプリケーションの設計能力が評価されます。このガイドでは、基礎から本番環境の高度な概念まで、最も頻繁に問われる質問を網羅しています。

面接のヒント

採用担当者は、理論と実践的な例を組み合わせた回答を高く評価します。各質問に対して、コードや具体的なユースケースで説明することで、実務経験が伝わります。

Node.jsの基礎

質問1:Event Loopとは何か、どのように動作するか?

Event Loopは、Node.jsがシングルスレッドで動作しながらも非同期操作をノンブロッキングで処理するための中核メカニズムです。JavaScriptコード、コールバック、イベントの実行を調整します。

event-loop-demo.jsjavascript
// Demonstration of Event Loop execution order

console.log('1. Script start (synchronous)');

// setTimeout goes to the Timer Queue
setTimeout(() => {
  console.log('5. setTimeout callback (Timer Queue)');
}, 0);

// setImmediate goes to the Check Queue
setImmediate(() => {
  console.log('6. setImmediate callback (Check Queue)');
});

// Promise goes to the Microtask Queue (priority)
Promise.resolve().then(() => {
  console.log('3. Promise.then (Microtask Queue)');
});

// process.nextTick has the highest priority
process.nextTick(() => {
  console.log('2. process.nextTick (nextTick Queue)');
});

console.log('4. Script end (synchronous)');

// Output order: 1, 4, 2, 3, 5, 6

Event Loopはキューの処理において正確な順序に従います。まず同期コード、次にnextTick、マイクロタスク(Promise)、タイマー、I/Oコールバック、setImmediate、最後にクローズコールバックの順です。

質問2:process.nextTick()とsetImmediate()の違いは?

この質問は、Event Loopにおける実行優先度の詳細な理解を評価します。

nextTick-vs-immediate.jsjavascript
// Behavior comparison

// process.nextTick executes BEFORE the next Event Loop phase
process.nextTick(() => {
  console.log('nextTick 1');
  process.nextTick(() => {
    console.log('nextTick 2 (nested)');
  });
});

// setImmediate executes in the Check phase of the Event Loop
setImmediate(() => {
  console.log('setImmediate 1');
  setImmediate(() => {
    console.log('setImmediate 2 (nested)');
  });
});

// Output: nextTick 1, nextTick 2, setImmediate 1, setImmediate 2

process.nextTick()は現在の操作の直後、Event Loopが続行する前に処理されます。過度な使用はEvent Loopをブロックする可能性があります。setImmediate()はより予測可能で、実行の延期に推奨されます。

スタベーションに注意

process.nextTick()の再帰呼び出しはEvent Loopを枯渇させ、I/O処理を妨げる可能性があります。重要でない操作にはsetImmediate()の使用が推奨されます。

質問3:Node.jsは非同期コードのエラーをどのように処理するか?

非同期エラー処理は同期コードとは根本的に異なります。適切な処理がなければ、エラーがアプリケーションをクラッシュさせる可能性があります。

error-handling.jsjavascript
// Asynchronous error handling patterns

// Pattern 1: Callbacks with error-first convention
function readFileCallback(path, callback) {
  const fs = require('fs');
  fs.readFile(path, 'utf8', (err, data) => {
    if (err) {
      // Error is ALWAYS the first argument
      return callback(err, null);
    }
    callback(null, data);
  });
}

// Pattern 2: Promises with catch
async function readFilePromise(path) {
  const fs = require('fs').promises;
  try {
    const data = await fs.readFile(path, 'utf8');
    return data;
  } catch (err) {
    // Centralized error handling
    console.error(`File read error: ${err.message}`);
    throw err; // Re-throw for propagation
  }
}

// Pattern 3: Global handling of unhandled rejections
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled rejection:', reason);
  // In production: log and graceful shutdown
});

// Pattern 4: Handling uncaught exceptions
process.on('uncaughtException', (err) => {
  console.error('Uncaught exception:', err);
  // CRITICAL: always terminate the process after
  process.exit(1);
});

本番環境では、すべてのPromiseに.catch()があるか、try/catchブロック内にある必要があります。グローバルハンドラーはセーフティネットとして機能し、主要な解決策ではありません。

非同期プログラミングと並行処理

質問4:Node.jsにおける並列処理と並行処理の違いを説明せよ

Node.jsはデフォルトで並行処理が可能ですが、並列処理は行いません。この区別はパフォーマンスの理解に不可欠です。

concurrency-vs-parallelism.jsjavascript
// CONCURRENCY: multiple tasks progress by alternating (single-thread)
async function concurrentTasks() {
  console.time('concurrent');

  // These calls are concurrent, not parallel
  const results = await Promise.all([
    fetch('https://api.example.com/users'),    // Non-blocking I/O
    fetch('https://api.example.com/products'), // Non-blocking I/O
    fetch('https://api.example.com/orders'),   // Non-blocking I/O
  ]);

  console.timeEnd('concurrent'); // ~time of the longest request
  return results;
}

// PARALLELISM: with Worker Threads for CPU-bound tasks
const { Worker, isMainThread, parentPort } = require('worker_threads');

if (isMainThread) {
  // Main thread delegates CPU-intensive work
  async function parallelComputation() {
    console.time('parallel');

    const workers = [
      createWorker({ start: 0, end: 1000000 }),
      createWorker({ start: 1000000, end: 2000000 }),
      createWorker({ start: 2000000, end: 3000000 }),
    ];

    const results = await Promise.all(workers);
    console.timeEnd('parallel');
    return results.reduce((a, b) => a + b, 0);
  }

  function createWorker(data) {
    return new Promise((resolve, reject) => {
      const worker = new Worker(__filename, { workerData: data });
      worker.on('message', resolve);
      worker.on('error', reject);
    });
  }
} else {
  // Code executed in the Worker Thread
  const { workerData } = require('worker_threads');
  let sum = 0;
  for (let i = workerData.start; i < workerData.end; i++) {
    sum += Math.sqrt(i); // CPU-intensive calculation
  }
  parentPort.postMessage(sum);
}

I/Oバウンド操作(ネットワーク、ファイル)にはネイティブの並行処理で十分です。CPUバウンドタスク(重い計算、暗号処理)には、Worker Threadsが真の並列処理を実現します。

質問5:Clusterモジュールはどのように機能するか?

Clusterモジュールは、同じポートを共有する複数のNode.jsプロセスを作成し、利用可能なすべてのCPUコアを活用します。

cluster-example.jsjavascript
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} is running`);
  console.log(`Forking ${numCPUs} workers...`);

  // Fork one worker per CPU core
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  // Handle crashing workers
  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died (${signal || code})`);
    console.log('Starting a new worker...');
    cluster.fork(); // Automatic restart
  });

  // Inter-process communication
  cluster.on('message', (worker, message) => {
    console.log(`Message from worker ${worker.id}:`, message);
  });

} else {
  // Workers share the TCP port
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end(`Handled by worker ${process.pid}\n`);

    // Send stats to primary
    process.send({ type: 'request', pid: process.pid });
  }).listen(8000);

  console.log(`Worker ${process.pid} started`);
}

ロードバランシングはOS(Linux/macOSではラウンドロビン)が自動的に処理します。本番環境では、PM2が内蔵のクラスターモードでこの管理を簡素化します。

Node.js / NestJSの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

StreamsとBuffers

質問6:クラシックなメソッドの代わりにStreamsを使用すべき場合は?

Streamsは、すべてをメモリにロードする代わりにチャンク単位でデータを処理します。大きなファイルやストリーミングシナリオに不可欠です。

streams-comparison.jsjavascript
const fs = require('fs');

// ❌ BAD: loads entire file into memory
async function readEntireFile(path) {
  const data = await fs.promises.readFile(path); // Blocks if file > RAM
  return processData(data);
}

// ✅ GOOD: chunk-based processing with Stream
function readWithStream(path) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    const readStream = fs.createReadStream(path, {
      highWaterMark: 64 * 1024, // 64KB per chunk
    });

    readStream.on('data', (chunk) => {
      // Progressive processing, constant memory
      chunks.push(processChunk(chunk));
    });

    readStream.on('end', () => resolve(chunks));
    readStream.on('error', reject);
  });
}

// ✅ BEST: pipeline for chaining transformations
const { pipeline } = require('stream/promises');
const zlib = require('zlib');

async function compressFile(input, output) {
  await pipeline(
    fs.createReadStream(input),   // Source
    zlib.createGzip(),            // Transform
    fs.createWriteStream(output)  // Destination
  );
  // Automatic error handling and backpressure management
}

データサイズが数MBを超える可能性がある場合、またはリアルタイム処理(アップロード、ログ、ネットワークデータ)にはStreamsの使用が推奨されます。

質問7:バックプレッシャーの概念を説明せよ

バックプレッシャーは、データプロデューサーがコンシューマーよりも高速な場合に発生します。管理しなければメモリが溢れます。

backpressure-demo.jsjavascript
const fs = require('fs');

// ❌ Problem: no backpressure handling
function badCopy(src, dest) {
  const readable = fs.createReadStream(src);
  const writable = fs.createWriteStream(dest);

  readable.on('data', (chunk) => {
    // If write() returns false, the internal buffer is full
    // But here reading continues anyway → memory leak
    writable.write(chunk);
  });
}

// ✅ Solution: respect the writable signal
function goodCopy(src, dest) {
  const readable = fs.createReadStream(src);
  const writable = fs.createWriteStream(dest);

  readable.on('data', (chunk) => {
    const canContinue = writable.write(chunk);

    if (!canContinue) {
      // Pause reading until buffer drains
      readable.pause();
    }
  });

  writable.on('drain', () => {
    // Buffer drained, resume reading
    readable.resume();
  });

  readable.on('end', () => writable.end());
}

// ✅ BEST: pipe() handles everything automatically
function bestCopy(src, dest) {
  const readable = fs.createReadStream(src);
  const writable = fs.createWriteStream(dest);

  // pipe() handles backpressure natively
  readable.pipe(writable);
}

pipe()またはpipeline()メソッドがバックプレッシャーを自動的に処理します。複雑なケースでは、pause/resumeロジックを手動で実装します。

パフォーマンスと最適化

質問8:メモリリークの特定と修正方法は?

メモリリークはNode.jsでよく発生します。本番環境では検出と修正が不可欠です。

memory-leak-patterns.jsjavascript
// ❌ Leak 1: closures that retain references
function createLeakyHandler() {
  const hugeData = Buffer.alloc(100 * 1024 * 1024); // 100MB

  return function handler(req, res) {
    // hugeData remains in memory as long as handler exists
    res.end('Hello');
  };
}

// ✅ Fix: limit the scope
function createSafeHandler() {
  return function handler(req, res) {
    // Data created and released on each request
    const data = fetchData();
    res.end(data);
  };
}

// ❌ Leak 2: event listeners not cleaned up
class LeakyClass {
  constructor() {
    // Added on each instantiation, never removed
    process.on('message', this.handleMessage);
  }
  handleMessage(msg) { /* ... */ }
}

// ✅ Fix: explicit cleanup
class SafeClass {
  constructor() {
    this.boundHandler = this.handleMessage.bind(this);
    process.on('message', this.boundHandler);
  }
  handleMessage(msg) { /* ... */ }

  destroy() {
    // Mandatory cleanup
    process.removeListener('message', this.boundHandler);
  }
}

// Diagnostics with native tools
function diagnoseMemory() {
  const used = process.memoryUsage();
  console.log({
    heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)}MB`,
    external: `${Math.round(used.external / 1024 / 1024)}MB`,
    rss: `${Math.round(used.rss / 1024 / 1024)}MB`,
  });
}

// Enable manual garbage collector for testing
// node --expose-gc app.js
if (global.gc) {
  global.gc();
  diagnoseMemory();
}

本番環境では、clinic.js、Chrome DevToolsのヒープスナップショット、またはDataDogやNew RelicなどのAPMソリューションが使用されます。

質問9:Node.js APIのパフォーマンスを最適化する方法は?

この質問は複数レベルの最適化技術に関する知識を評価します。

performance-optimization.jsjavascript
// 1. CACHING: reduce expensive calls
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 300 }); // 5-minute TTL

async function getCachedUser(id) {
  const cacheKey = `user:${id}`;
  let user = cache.get(cacheKey);

  if (!user) {
    user = await db.users.findById(id);
    cache.set(cacheKey, user);
  }

  return user;
}

// 2. CONNECTION POOLING: reuse DB connections
const { Pool } = require('pg');
const pool = new Pool({
  max: 20,                // Max simultaneous connections
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

// 3. COMPRESSION: reduce response size
const compression = require('compression');
app.use(compression({
  filter: (req, res) => {
    // Only compress if > 1KB
    return compression.filter(req, res);
  },
  threshold: 1024,
}));

// 4. BATCHING: group operations
async function batchInsert(items) {
  const BATCH_SIZE = 1000;

  for (let i = 0; i < items.length; i += BATCH_SIZE) {
    const batch = items.slice(i, i + BATCH_SIZE);
    await db.items.insertMany(batch);
  }
}

// 5. LAZY LOADING: load on demand
async function getUserWithPosts(userId, includePosts = false) {
  const user = await db.users.findById(userId);

  if (includePosts) {
    user.posts = await db.posts.findByUserId(userId);
  }

  return user;
}

最適化はプロファイリングに基づいて行うべきです。最適化の前に計測することで、実際のボトルネックを特定できます。

80/20の法則

パフォーマンス問題の80%はコードの20%から発生します。プロファイリングにより、闇雲に最適化する前にこれらの重要な領域を特定できます。

セキュリティ

質問10:Node.js APIを一般的な攻撃から守る方法は?

セキュリティは面接で繰り返し出るテーマです。OWASPの脆弱性に関する知識の提示が求められます。

security-best-practices.jsjavascript
const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');

const app = express();

// 1. SECURITY HEADERS with Helmet
app.use(helmet());

// 2. RATE LIMITING against brute-force attacks
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100,                  // 100 requests per IP
  message: 'Too many requests, please try again later',
  standardHeaders: true,
  legacyHeaders: false,
});
app.use('/api/', limiter);

// 3. SANITIZATION against NoSQL injections
app.use(mongoSanitize());

// 4. XSS PROTECTION
app.use(xss());

// 5. STRICT INPUT VALIDATION
const { body, validationResult } = require('express-validator');

app.post('/api/users',
  [
    body('email').isEmail().normalizeEmail(),
    body('password').isLength({ min: 8 }).escape(),
    body('name').trim().escape(),
  ],
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    // Continue processing
  }
);

// 6. SQL INJECTION PROTECTION (with parameters)
async function safeQuery(userId) {
  // ✅ Parameterized query
  const result = await pool.query(
    'SELECT * FROM users WHERE id = $1',
    [userId]
  );
  return result.rows;
}

// ❌ NEVER string concatenation
async function unsafeQuery(userId) {
  // Vulnerable to SQL injection
  const result = await pool.query(
    `SELECT * FROM users WHERE id = ${userId}`
  );
}

本番環境では、制限的なCORS、必須HTTPS、セキュリティログ、シークレットローテーション、定期的な依存関係監査(npm audit)も追加する必要があります。

アーキテクチャとデザインパターン

質問11:Node.jsにおけるRepositoryパターンを説明せよ

Repositoryパターンはデータアクセスを抽象化し、テストと保守性を容易にします。

repository-pattern.jsjavascript
// Abstract interface (for TypeScript, or documentation)
class UserRepository {
  async findById(id) { throw new Error('Not implemented'); }
  async findByEmail(email) { throw new Error('Not implemented'); }
  async create(userData) { throw new Error('Not implemented'); }
  async update(id, userData) { throw new Error('Not implemented'); }
  async delete(id) { throw new Error('Not implemented'); }
}

// Concrete implementation with Prisma
class PrismaUserRepository extends UserRepository {
  constructor(prisma) {
    super();
    this.prisma = prisma;
  }

  async findById(id) {
    return this.prisma.user.findUnique({ where: { id } });
  }

  async findByEmail(email) {
    return this.prisma.user.findUnique({ where: { email } });
  }

  async create(userData) {
    return this.prisma.user.create({ data: userData });
  }

  async update(id, userData) {
    return this.prisma.user.update({
      where: { id },
      data: userData,
    });
  }

  async delete(id) {
    return this.prisma.user.delete({ where: { id } });
  }
}

// Implementation for testing
class InMemoryUserRepository extends UserRepository {
  constructor() {
    super();
    this.users = new Map();
    this.idCounter = 1;
  }

  async findById(id) {
    return this.users.get(id) || null;
  }

  async create(userData) {
    const user = { id: this.idCounter++, ...userData };
    this.users.set(user.id, user);
    return user;
  }
  // ... other methods
}

// Service using the repository (dependency injection)
class UserService {
  constructor(userRepository) {
    this.userRepository = userRepository;
  }

  async getUser(id) {
    const user = await this.userRepository.findById(id);
    if (!user) throw new Error('User not found');
    return user;
  }
}

このパターンにより、ビジネスロジックを変更せずに永続化の実装を変更できます。

質問12:ジョブキューシステムの実装方法は?

キューは重いタスクを遅延させ、信頼性の高い実行を保証します。

job-queue.jsjavascript
const Queue = require('bull');

// Create queue with Redis as backend
const emailQueue = new Queue('email', {
  redis: {
    host: 'localhost',
    port: 6379,
  },
  defaultJobOptions: {
    attempts: 3,          // Number of attempts
    backoff: {
      type: 'exponential',
      delay: 2000,        // Initial delay between attempts
    },
    removeOnComplete: 100, // Keep last 100 completed jobs
  },
});

// Producer: add jobs to the queue
async function sendWelcomeEmail(userId, email) {
  await emailQueue.add('welcome', {
    userId,
    email,
    template: 'welcome',
  }, {
    priority: 1,          // High priority
    delay: 5000,          // 5-second delay
  });
}

// Consumer: process jobs
emailQueue.process('welcome', async (job) => {
  const { userId, email, template } = job.data;

  // Update progress
  job.progress(10);

  const html = await renderTemplate(template, { userId });
  job.progress(50);

  await sendEmail(email, 'Welcome!', html);
  job.progress(100);

  return { sent: true, email };
});

// Event handling
emailQueue.on('completed', (job, result) => {
  console.log(`Job ${job.id} completed:`, result);
});

emailQueue.on('failed', (job, err) => {
  console.error(`Job ${job.id} failed:`, err.message);
});

// Recurring jobs (cron)
emailQueue.add('newsletter', { type: 'weekly' }, {
  repeat: {
    cron: '0 9 * * MON', // Every Monday at 9am
  },
});

Bull + Redisが最も人気のあるソリューションです。より単純なニーズには、agendabee-queueが軽量な代替手段となります。

高度な質問

質問13:ネイティブN-APIモジュールはどのように機能するか?

N-APIにより、Node.jsバージョン間で安定したAPIを持つC/C++ネイティブモジュールを作成できます。

native-module.cppcpp
// Native module for CPU-intensive calculations

#include <napi.h>

// Synchronous function exposed to JavaScript
Napi::Number Fibonacci(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();

  // Argument validation
  if (info.Length() < 1 || !info[0].IsNumber()) {
    Napi::TypeError::New(env, "Number expected")
      .ThrowAsJavaScriptException();
    return Napi::Number::New(env, 0);
  }

  int n = info[0].As<Napi::Number>().Int32Value();

  // Iterative Fibonacci calculation
  long long a = 0, b = 1;
  for (int i = 0; i < n; i++) {
    long long temp = a + b;
    a = b;
    b = temp;
  }

  return Napi::Number::New(env, static_cast<double>(a));
}

// Module initialization
Napi::Object Init(Napi::Env env, Napi::Object exports) {
  exports.Set(
    Napi::String::New(env, "fibonacci"),
    Napi::Function::New(env, Fibonacci)
  );
  return exports;
}

NODE_API_MODULE(native_module, Init)
javascript
// Usage from JavaScript
const native = require('./build/Release/native_module');

// 10x faster than JavaScript equivalent
const result = native.fibonacci(50);

ネイティブモジュールは、集中的な計算、既存のC/C++ライブラリの統合、またはシステムAPIへのアクセスに有用です。

質問14:V8のGarbage Collectorを説明せよ

GCの理解は、ポーズとメモリ消費を最小化するコードの記述に役立ちます。

gc-optimization.jsjavascript
// V8 GC uses two spaces: Young and Old Generation

// 1. Young Generation: short-lived objects
function shortLivedObjects() {
  for (let i = 0; i < 1000; i++) {
    const temp = { data: i }; // Allocated then collected quickly
  }
  // Minor GC (Scavenge) very fast
}

// 2. Old Generation: objects that survive multiple GCs
const cache = new Map(); // Survives, promoted to Old Generation

// ❌ Problematic pattern: many promoted objects
function createManyLongLived() {
  const objects = [];
  for (let i = 0; i < 100000; i++) {
    objects.push({ id: i, data: new Array(100).fill(0) });
  }
  return objects; // All promoted to Old Gen = slow major GC
}

// ✅ Optimized pattern: object reuse
class ObjectPool {
  constructor(factory, size = 100) {
    this.pool = Array.from({ length: size }, factory);
    this.available = [...this.pool];
  }

  acquire() {
    return this.available.pop() || this.pool[0];
  }

  release(obj) {
    // Reset and return to pool
    Object.keys(obj).forEach(k => obj[k] = null);
    this.available.push(obj);
  }
}

// GC monitoring
const v8 = require('v8');

function getHeapStats() {
  const stats = v8.getHeapStatistics();
  return {
    totalHeap: `${Math.round(stats.total_heap_size / 1024 / 1024)}MB`,
    usedHeap: `${Math.round(stats.used_heap_size / 1024 / 1024)}MB`,
    heapLimit: `${Math.round(stats.heap_size_limit / 1024 / 1024)}MB`,
  };
}

--max-old-space-sizeフラグにより、メモリ集約型アプリケーションのOld Generationの制限を増加できます。

質問15:グレースフルシャットダウンの実装方法は?

グレースフルシャットダウンは、処理中のリクエストを完了し、サーバー停止前に接続を適切に閉じることを可能にします。

graceful-shutdown.jsjavascript
const http = require('http');

const server = http.createServer((req, res) => {
  // Simulate a long request
  setTimeout(() => {
    res.writeHead(200);
    res.end('Done');
  }, 2000);
});

// Tracking active connections
let connections = new Set();

server.on('connection', (conn) => {
  connections.add(conn);
  conn.on('close', () => connections.delete(conn));
});

// Graceful shutdown function
async function shutdown(signal) {
  console.log(`${signal} received, starting graceful shutdown...`);

  // 1. Stop accepting new connections
  server.close(() => {
    console.log('HTTP server closed');
  });

  // 2. Close idle connections
  for (const conn of connections) {
    conn.end();
  }

  // 3. Close DB connections, queues, etc.
  await Promise.all([
    database.disconnect(),
    redisClient.quit(),
    messageQueue.close(),
  ]);

  // 4. Safety timeout
  setTimeout(() => {
    console.error('Forced shutdown after timeout');
    process.exit(1);
  }, 30000);

  console.log('Graceful shutdown completed');
  process.exit(0);
}

// Listen for termination signals
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));

// Start server
server.listen(3000, () => {
  console.log('Server running on port 3000');
});

コンテナ(Docker、Kubernetes)を使用した本番環境では、グレースフルシャットダウンはゼロダウンタイムデプロイに不可欠です。

Node.js / NestJSの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

行動面の質問

質問16:解決したパフォーマンス問題を説明せよ

この質問は実務経験を評価します。STARフォーマット(状況、タスク、行動、結果)で回答を構成することが推奨されます。

構造化された回答例:

text
Situation: A reporting API was timing out on requests
            exceeding 100,000 records.

Task:       Reduce response time from 45s to under 5s.

Action:
1. Profiling with clinic.js → identified JSON serialization as bottleneck
2. Implemented streaming with Transform streams
3. Database-side pagination
4. Added Redis caching for frequent queries

Result:     Response time reduced to 2s, memory usage decreased by 10x.

質問17:依存関係とその更新をどのように管理するか?

package.json - Versioning best practicesjavascript
{
  "dependencies": {
    // ✅ Exact versions for production
    "express": "4.18.2",

    // ✅ Caret for compatible minor updates
    "lodash": "^4.17.21",

    // ❌ Avoid latest or *
    // "some-lib": "*"
  },
  "devDependencies": {
    // Quality tools
    "npm-check-updates": "^16.0.0"
  },
  "scripts": {
    // Vulnerability check
    "audit": "npm audit --audit-level=moderate",

    // Interactive update
    "update:check": "ncu",
    "update:apply": "ncu -u && npm install"
  },
  "engines": {
    // Specify required Node.js version
    "node": ">=20.0.0"
  }
}

package-lock.jsonの使用、DependabotまたはRenovateによる自動化、各メジャーアップデート前のリグレッションテストに言及することが推奨されます。

まとめ

Node.jsバックエンド面接では、内部メカニズムの理論的理解と本番環境の実際の問題を解決する能力の両方が評価されます。Event Loop、非同期パターン、最適化技術の習得は、シニアバックエンド開発者ポジションで期待される基盤を形成します。

準備チェックリスト

  • ✅ Event Loopの動作とそのフェーズを理解する
  • ✅ コールバック、Promise、async/awaitの違いを習得する
  • ✅ 非同期エラー処理パターンを知る
  • ✅ Streamsとクラシックメソッドの使い分けを知る
  • ✅ メモリリークを特定し修正する
  • ✅ OWASPセキュリティベストプラクティスを適用する
  • ✅ クラスタリングとグレースフルシャットダウンを実装する
  • ✅ プロファイリングツール(clinic.js、Chrome DevTools)を使用する

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

技術的な準備は実践的なプロジェクトで補完すべきです。本番用APIの構築、Node.jsオープンソースプロジェクトへの貢献、LeetCodeなどのプラットフォームでの課題解決が、これらの知識の定着に役立ちます。

タグ

#nodejs
#interview
#backend
#javascript
#technical interview

共有

関連記事