Node.jsプロジェクトでGitHub Actionsを使ったCI/CDパイプラインを構築したいけれど、「どこから始めればいいか分からない」「設定が複雑そう」と感じていませんか?この記事では、実際のNode.jsプロジェクトを例に、段階的にCI/CDパイプラインを構築する方法を詳しく解説します。
シリーズ記事:
- GitHub Actionsとは?CI/CDを自動化する開発者必須ツールの完全ガイド
- GitHub Actionsのワークフローファイル作成入門:YAML基礎から実践まで
- GitHub Actionsの基本用語完全解説:ワークフロー・ジョブ・ステップの違いを図解で理解
- GitHub Actions無料プランの制限と効率的な使い方:コスト最適化の実践テクニック
前提:サンプルNode.jsプロジェクトの準備
この記事では、以下の構成のExpressアプリケーションを例に進めます。
プロジェクト構成
my-node-app/
├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── deploy.yml
├── src/
│ ├── app.js
│ ├── routes/
│ └── utils/
├── tests/
│ ├── unit/
│ └── integration/
├── package.json
├── package-lock.json
├── .eslintrc.js
├── jest.config.js
└── README.md
package.json の設定例
{
"name": "my-node-app",
"version": "1.0.0",
"scripts": {
"start": "node src/app.js",
"dev": "nodemon src/app.js",
"test": "jest",
"test:coverage": "jest --coverage",
"test:watch": "jest --watch",
"lint": "eslint src/ tests/",
"lint:fix": "eslint src/ tests/ --fix",
"build": "npm run lint && npm test",
"security-audit": "npm audit"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"helmet": "^7.0.0"
},
"devDependencies": {
"jest": "^29.5.0",
"supertest": "^6.3.3",
"eslint": "^8.42.0",
"nodemon": "^2.0.22"
}
}
段階1:基本的なCIワークフローの構築
最初のワークフロー:単純なテスト実行
.github/workflows/ci.yml
を作成します:
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
ワークフローの動作確認
- 上記ファイルをリポジトリにコミット
- GitHub の「Actions」タブで実行状況を確認
- 各ステップのログを確認してエラーがないかチェック
段階2:複数Node.jsバージョンでのテスト
マトリックス戦略の導入
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Display Node.js version
run: node --version
条件分岐による最適化
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
# カバレッジは最新版でのみ実行(効率化)
- name: Run tests with coverage
if: matrix.node-version == '20'
run: npm run test:coverage
# セキュリティ監査も最新版でのみ実行
- name: Security audit
if: matrix.node-version == '20'
run: npm run security-audit
段階3:リンティングとコード品質チェック
ESLintの統合
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
Prettierの追加
code-style:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Check code formatting
run: npx prettier --check "src/**/*.js" "tests/**/*.js"
- name: Run ESLint
run: npm run lint
段階4:テストカバレッジとレポート
カバレッジレポートの生成と保存
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests with coverage
run: npm run test:coverage
- name: Upload coverage reports
uses: actions/upload-artifact@v3
with:
name: coverage-report
path: coverage/
retention-days: 7
- name: Display coverage summary
run: |
echo "## Coverage Report" >> $GITHUB_STEP_SUMMARY
cat coverage/lcov-report/index.html | grep -A 10 "Coverage" >> $GITHUB_STEP_SUMMARY
Codecovとの連携
- name: Upload to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
flags: unittests
name: codecov-umbrella
fail_ci_if_error: true
段階5:ビルドプロセスの自動化
アプリケーションビルドの追加
build:
needs: [lint, test] # lint と test が成功後に実行
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Create deployment package
run: |
mkdir -p deploy
cp -r src/ deploy/
cp package*.json deploy/
cd deploy && npm ci --production
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: app-build
path: deploy/
retention-days: 30
段階6:セキュリティとパフォーマンスチェック
セキュリティ脆弱性の検査
security:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run security audit
run: npm audit --audit-level moderate
- name: Run Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
Docker セキュリティスキャン
docker-security:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t my-app:latest .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'my-app:latest'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
段階7:デプロイメント自動化
Herokuへのデプロイ
.github/workflows/deploy.yml
を作成:
name: Deploy
on:
push:
branches: [main]
release:
types: [published]
jobs:
deploy-staging:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Deploy to Heroku staging
uses: akhileshns/heroku-deploy@v3.12.12
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_app_name: "my-app-staging"
heroku_email: ${{ secrets.HEROKU_EMAIL }}
deploy-production:
if: github.event_name == 'release'
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Deploy to Heroku production
uses: akhileshns/heroku-deploy@v3.12.12
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_app_name: "my-app-production"
heroku_email: ${{ secrets.HEROKU_EMAIL }}
Vercelへのデプロイ
deploy-vercel:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build for production
run: npm run build
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.ORG_ID }}
vercel-project-id: ${{ secrets.PROJECT_ID }}
vercel-args: '--prod'
段階8:通知とモニタリング
Slack通知の追加
notify:
needs: [deploy-production]
runs-on: ubuntu-latest
if: always()
steps:
- name: Notify Slack
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
channel: '#deployments'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
fields: repo,message,commit,author,action,eventName,ref,workflow
GitHub Issue作成(失敗時)
- name: Create issue on failure
if: failure()
uses: actions/github-script@v6
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Deployment failed: ' + context.sha,
body: 'Deployment failed for commit ' + context.sha + '. Please check the workflow logs.',
labels: ['bug', 'deployment']
})
完全版ワークフローファイル
CI ワークフロー(.github/workflows/ci.yml)
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '18'
jobs:
lint:
name: Code Quality
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Check code formatting
run: npx prettier --check "src/**/*.js" "tests/**/*.js"
- name: Run ESLint
run: npm run lint
test:
name: Test
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Run tests with coverage
if: matrix.node-version == '20'
run: npm run test:coverage
- name: Upload coverage reports
if: matrix.node-version == '20'
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
security:
name: Security Scan
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run security audit
run: npm audit --audit-level moderate
build:
name: Build
needs: [lint, test, security]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: app-build-${{ github.sha }}
path: dist/
retention-days: 7
トラブルシューティング
よくある問題と解決方法
1. npm ci が失敗する
原因:package-lock.json と package.json の不整合
解決:
# ローカルで package-lock.json を再生成
rm package-lock.json
npm install
git add package-lock.json
git commit -m "Fix package-lock.json"
2. テストがローカルでは通るがCIで失敗
原因:環境差異、タイムゾーン問題、ファイルパス問題
解決:
- name: Debug environment
run: |
echo "Node version: $(node --version)"
echo "NPM version: $(npm --version)"
echo "Working directory: $(pwd)"
echo "Files in directory:"
ls -la
echo "Environment variables:"
env | grep NODE
3. キャッシュが効かない
原因:キャッシュキーの設定ミス
解決:
- name: Cache node modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
パフォーマンス最適化のベストプラクティス
1. 依存関係インストールの高速化
- name: Setup Node.js with cache
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm' # ビルトインキャッシュを活用
- name: Install dependencies
run: npm ci --prefer-offline # オフラインモード優先
2. 並列実行の活用
jobs:
lint:
# ...
test:
# ...
security:
# ...
# これらは並列実行される
build:
needs: [lint, test, security] # 上記完了後に実行
# ...
3. 条件付き実行の活用
- name: Integration tests (main branch only)
if: github.ref == 'refs/heads/main'
run: npm run test:integration
- name: E2E tests (release only)
if: github.event_name == 'release'
run: npm run test:e2e
まとめ
Node.jsプロジェクトでのGitHub Actions CI/CD構築のポイント:
段階的な構築
- シンプルから開始:基本的なテスト実行から始める
- 機能を順次追加:リンティング→カバレッジ→セキュリティ→デプロイ
- 継続的な改善:実際の運用で見つかった問題を解決
効率的な設定
- 適切なキャッシュ利用:npm キャッシュとビルドキャッシュ
- 並列実行の活用:独立したジョブは並列で実行
- 条件分岐の使用:必要な時だけ重い処理を実行
運用面の考慮
- 適切な通知設定:成功・失敗の適切な通知
- セキュリティ対策:依存関係とコンテナのスキャン
- モニタリング:実行時間と使用量の監視
この設定により、Node.jsプロジェクトで高品質なCI/CDパイプラインを構築できます。次回は、Python プロジェクトでの具体的な実装方法を詳しく解説していきます!