Node.js: Deploy and Optimize

Last updated: 2026-08-26

After Charlie deployed the API to the production server using node app.js, the process would frequently crash due to uncaught exceptions, causing prolonged service outages during unattended operation. He implemented PM2 to enable automatic restart and cluster mode, combined with Docker containerization and an Nginx reverse proxy, which boosted service availability from 95% to 99.9%—and he was finally able to sleep soundly.

You'll learn:


1. PM2 Process Management

(1) Why Do We Need a Process Manager?

In a production environment, Node.js processes may crash due to uncaught exceptions, memory leaks, or insufficient system resources. If started directly with node app.js, the process will not be restarted after a crash, resulting in service interruption. PM2 is the most popular process manager for Node.js, offering automatic restart, log management, load balancing, and monitoring features.

▶ Example: (2) Installation and Basic Commands

BASH
npm install -g pm2
BASH
pm2 start app.js --name "my-api"
pm2 restart my-api
pm2 stop my-api
pm2 delete my-api
pm2 logs my-api
pm2 monit

(3) Quick Reference for Common PM2 Commands

Command Function Common Use Cases
pm2 start app.js Launch the App First Deployment
pm2 restart <name> Restart the app After updating the code
pm2 reload <name> Zero-Downtime Restart Production Environment Update
pm2 stop <name> Service Suspended During Maintenance
pm2 delete <name> Delete Process Completely Remove
pm2 logs [name] View Log Troubleshoot
pm2 monit Real-time Monitoring Dashboard Monitor Resource Usage
pm2 list Process List View Running Status
pm2 describe <name> Process Details In-Depth Diagnostics
pm2 save Save Process List Startup Configuration
pm2 startup Generate a startup script Automatically restore after server restart

▶ Example: PM2 Startup and Monitoring

BASH
pm2 start app.js --name "charlie-api" -i max
pm2 monit
pm2 save
pm2 startup
TEXT 📖 Display only
┌─────┬──────────────┬─────────────┬─────────┬─────────┬──────────┐
│ id  │ name         │ mode        │ ↺       │ status  │ cpu      │
├─────┼──────────────┼─────────────┼─────────┼─────────┼──────────┤
│ 0   │ charlie-api  │ cluster     │ 15      │ online  │ 12%      │
│ 1   │ charlie-api  │ cluster     │ 2       │ online  │ 8%       │
│ 2   │ charlie-api  │ cluster     │ 0       │ online  │ 5%       │
│ 3   │ charlie-api  │ cluster     │ 1       │ online  │ 3%       │
└─────┴──────────────┴─────────────┴─────────┴─────────┴──────────┘


2. Docker Basics

(1) Why Use Docker

Docker packages applications and their dependencies into container images, ensuring consistency across development, testing, and production environments. Charlie’s code, which had previously run successfully on his local machine, threw errors after being deployed to the server due to differences in Node.js versions, but Docker completely resolved this issue.

▶ Example: (2) Writing a Dockerfile

DOCKERFILE
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=3s \
  CMD node healthcheck.js

USER node

CMD ["node", "app.js"]

▶ Example: (3) Docker Compose Multi-Service Orchestration

YAML
version: "3.8"

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - MONGO_URI=mongodb://mongo:27017/myapp
      - REDIS_URL=redis://redis:6379
    depends_on:
      - mongo
      - redis
    restart: always
    healthcheck:
      test: ["CMD", "node", "healthcheck.js"]
      interval: 30s
      timeout: 3s
      retries: 3

  mongo:
    image: mongo:7
    volumes:
      - mongo-data:/data/db
    restart: always

  redis:
    image: redis:7-alpine
    restart: always

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - api
    restart: always

volumes:
  mongo-data:

(4) Comparison of Deployment Methods

Dimension Bare-metal deployment Docker deployment Cloud service deployment
Environmental Consistency Poor Excellent Excellent
Deployment Speed Slow Fast Fastest
Operations Complexity High Medium Low
Resource Utilization High Medium On-Demand
Expansion Flexibility Poor Good Excellent
Cost Low Medium High
Use Cases Small Projects Medium and Large Projects Enterprise-Level Projects
Learning Curve Low Medium High

▶ Example: Building and Running a Docker Image

BASH
docker build -t charlie-api:1.0 .
docker run -d -p 3000:3000 --env-file .env charlie-api:1.0
docker-compose up -d
docker-compose logs -f api


3. Environment Variable Management

(1) Why Are Environment Variables Needed?

Configuration settings such as database addresses, port numbers, and passwords vary across different environments (development, testing, and production). Hard-coding these settings creates security risks and makes it difficult to switch between environments. Environment variables separate configuration from code, in line with the Twelve-Factor App methodology.

▶ Example: (2) dotenv and .env Files

JAVASCRIPT
const dotenv = require('dotenv');

dotenv.config({ path: `.env.${process.env.NODE_ENV || 'development'}` });

const config = {
  port: parseInt(process.env.PORT, 10) || 3000,
  mongoUri: process.env.MONGO_URI,
  jwtSecret: process.env.JWT_SECRET,
  redisUrl: process.env.REDIS_URL,
  logLevel: process.env.LOG_LEVEL || 'info',
};

module.exports = config;
▶ Try it Yourself

▶ Example: (3) Production Environment Configuration Strategy

TEXT 📖 Display only
.env                # Default Configuration(Do not submit Git)
.env.development    # Development Environment
.env.test           # Test Environment
.env.production     # Production Environment(Through CI/CD Inject,Do not commit to the code repository)

(4) Comparison of Environment Variable Management Methods

Method Security Flexibility Team Collaboration Use Cases
.env File Medium High Good General Projects
System Environment Variables High Low Poor Easy Deployment
Docker secrets High Medium Good Docker environment
K8s Secrets High High Excellent Kubernetes
Cloud Platform Configuration Center High High Excellent Cloud-Native Projects

▶ Example: Loading Multi-Environment Configurations

JAVASCRIPT
const path = require('path');
const dotenv = require('dotenv');

const env = process.env.NODE_ENV || 'development';

const envFile = path.resolve(process.cwd(), `.env.${env}`);
dotenv.config({ path: envFile });

if (env === 'development') {
  dotenv.config({ path: path.resolve(process.cwd(), '.env') });
}

const required = ['MONGO_URI', 'JWT_SECRET'];
const missing = required.filter(key => !process.env[key]);

if (missing.length) {
  throw new Error(`Missing required env vars: ${missing.join(', ')}`);
}

module.exports = {
  env,
  port: parseInt(process.env.PORT, 10) || 3000,
  mongoUri: process.env.MONGO_URI,
  jwtSecret: process.env.JWT_SECRET,
  redisUrl: process.env.REDIS_URL,
  logLevel: process.env.LOG_LEVEL || 'info',
};
▶ Try it Yourself

4. Cluster Mode

(1) Why Do We Need Clusters?

Node.js is single-threaded, meaning only one instance can run on a single CPU core. Cluster mode uses the cluster module to create multiple worker processes, fully utilizing multi-core CPUs to significantly improve throughput and availability.

(2) How the cluster Module Works

The master process is responsible for listening on the port and distributing requests, while the worker processes handle the actual business logic. Multiple worker processes share the same port to achieve load balancing.

▶ Example: (3) Manually Setting Up a Cluster

JAVASCRIPT
const cluster = require('cluster');
const os = require('os');
const http = require('http');

if (cluster.isPrimary) {
  const numCPUs = os.cpus().length;
  console.log(`Master ${process.pid} is running`);
  console.log(`Forking ${numCPUs} workers...`);

  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died. Restarting...`);
    cluster.fork();
  });
} else {
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end(`Handled by worker ${process.pid}\n`);
  }).listen(3000);

  console.log(`Worker ${process.pid} started`);
}
▶ Try it Yourself

(4) PM2 Cluster Mode

PM2 has built-in cluster support that can be enabled without modifying the code:

BASH
pm2 start app.js -i max
pm2 start app.js -i 4

▶ Example: Performance Comparison of Cluster Modes

BASH
node single.js &
ab -n 10000 -c 100 http://localhost:3000/
TEXT 📖 Display only
Single process:
  Requests per second:    3254.21 [#/sec]

Cluster (4 workers):
  Requests per second:    11280.67 [#/sec]


5. Performance Analysis

(1) Basic Timing with console.time

The simplest way to measure performance, ideal for quickly identifying slow operations:

JAVASCRIPT
app.get('/api/users', async (req, res) => {
  console.time('fetch-users');
  const users = await User.find().lean();
  console.timeEnd('fetch-users');
  res.json(users);
});

(2) Performance Hooks: Precise Measurement

The built-in perf_hooks module in Node.js provides high-precision timing:

JAVASCRIPT
const { performance, PerformanceObserver } = require('perf_hooks');

const obs = new PerformanceObserver((list) => {
  const entries = list.getEntries();
  entries.forEach((entry) => {
    console.log(`${entry.name}: ${entry.duration.toFixed(2)}ms`);
  });
});
obs.observe({ type: 'measure', buffered: true });

function measureAsync(label, fn) {
  return async (...args) => {
    performance.mark(`${label}-start`);
    const result = await fn(...args);
    performance.mark(`${label}-end`);
    performance.measure(label, `${label}-start`, `${label}-end`);
    return result;
  };
}

const fastQuery = measureAsync('db-query', async () => {
  return await User.find().lean();
});

(3) Clinic.js Professional Diagnosis

Clinic.js is the official performance diagnostic toolkit recommended by Node.js:

BASH
npm install -g clinic
clinic doctor -- node app.js
clinic flame -- node app.js
clinic bubbleprof -- node app.js

(4) Comparison of Performance Optimization Strategies

Strategy Tools/Methods Applicable Scenarios Difficulty Effectiveness
Timing Analysis console.time Quick Locate Low Medium
Accurate Measurement perf_hooks Critical Path Medium High
CPU Analysis Clinic Flame CPU-Intensive Medium High
Event Loop Clinic Doctor I/O Blocking Medium High
Memory Leak heapdump / memwatch Memory Issues High High
Load Testing autocannon / ab Capacity Planning Low Medium
APM Monitoring New Relic / Datadog Continuous Monitoring Medium High

▶ Example: Using autocannon for load testing

BASH
npm install -g autocannon
autocannon -c 100 -d 10 http://localhost:3000/api/health
TEXT 📖 Display only
┌─────────┬──────┬──────┬───────┬──────┬───────┬───────┬───────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg   │ Stdev │ Max   │
├─────────┼──────┼──────┼───────┼──────┼───────┼───────┼───────┤
│ Latency │ 2 ms │ 4 ms │ 12 ms │ 18ms │ 5 ms  │ 3 ms  │ 45 ms │
└─────────┴──────┴──────┴───────┴──────┴───────┴───────┴───────┘

Requests/sec: 18523.6


6. Health Check Endpoints

(1) Why Are Health Checkups Necessary?

Container orchestration systems (Docker, K8s) and load balancers need to know whether a service is running normally. Health check endpoints provide a standardized interface for detecting service status, enabling automatic fault detection and traffic switching.

▶ Example: (2) Basic Health Examination

JAVASCRIPT
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
▶ Try it Yourself

▶ Example: (3) In-Depth Health Check

JAVASCRIPT
app.get('/health', async (req, res) => {
  const checks = {
    server: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    memory: process.memoryUsage(),
  };

  try {
    await mongoose.connection.db.admin().ping();
    checks.database = 'ok';
  } catch (err) {
    checks.database = 'error';
  }

  try {
    await redisClient.ping();
    checks.redis = 'ok';
  } catch (err) {
    checks.redis = 'error';
  }

  const isHealthy = checks.database === 'ok' && checks.redis === 'ok';
  res.status(isHealthy ? 200 : 503).json(checks);
});
▶ Try it Yourself

(4) Separation of readiness and liveness

In production environments, health checks are typically divided into two types: liveness (whether the process is alive) and readiness (whether it is ready to receive traffic):

JAVASCRIPT
app.get('/healthz', (req, res) => {
  res.json({ status: 'alive' });
});

app.get('/readyz', async (req, res) => {
  try {
    await mongoose.connection.db.admin().ping();
    await redisClient.ping();
    res.json({ status: 'ready' });
  } catch {
    res.status(503).json({ status: 'not ready' });
  }
});

▶ Example: Docker Health Check Script

JAVASCRIPT
const http = require('http');

const options = {
  hostname: 'localhost',
  port: process.env.PORT || 3000,
  path: '/healthz',
  timeout: 2000,
};

const req = http.request(options, (res) => {
  if (res.statusCode === 200) {
    process.exit(0);
  } else {
    process.exit(1);
  }
});

req.on('error', () => process.exit(1));
req.on('timeout', () => { req.destroy(); process.exit(1); });
req.end();
▶ Try it Yourself

7. Nginx Reverse Proxy

(1) Why Do We Need Nginx?

Nginx acts as a reverse proxy, providing features such as SSL termination, load balancing, static resource serving, and request throttling. Node.js focuses on business logic, while Nginx handles network-layer optimization; each performs its own specific role.

▶ Example: (2) Node.js Production Deployment Architecture

100%
graph LR
    Client[Client] --> Nginx[Nginx Reverse Proxy<br/>:80/:443]
    Nginx --> PM2[PM2 Cluster Management]
    PM2 --> W1[Worker 1<br/>:3000]
    PM2 --> W2[Worker 2<br/>:3000]
    PM2 --> W3[Worker 3<br/>:3000]
    PM2 --> W4[Worker 4<br/>:3000]
    W1 --> DB[(MongoDB)]
    W2 --> DB
    W3 --> Redis[(Redis)]
    W4 --> Redis

(3) Nginx configuration

TEXT 📖 Display only
upstream nodejs_backend {
    least_conn;
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    keepalive 64;
}

server {
    listen 80;
    server_name api.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate     /etc/ssl/certs/api.example.com.crt;
    ssl_certificate_key /etc/ssl/certs/api.example.com.key;

    location / {
        proxy_pass http://nodejs_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }

    location /health {
        proxy_pass http://nodejs_backend/health;
        access_log off;
    }

    location /static/ {
        alias /app/public/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
    limit_req zone=api burst=50 nodelay;
}

▶ Example: Nginx Reverse Proxy Configuration in Docker

YAML
# docker-compose.yml Excerpt
nginx:
  image: nginx:alpine
  ports:
    - "80:80"
    - "443:443"
  volumes:
    - ./nginx.conf:/etc/nginx/nginx.conf:ro
    - ./ssl:/etc/ssl/certs:ro
  depends_on:
    api:
      condition: service_healthy
  restart: always


8. Comprehensive Example: Complete Deployment Configuration

Charlie consolidated all deployment configurations into a single system, automating the entire process from code to production.

▶ Example: Project Directory Structure

TEXT 📖 Display only
charlie-api/
├── app.js
├── healthcheck.js
├── config/
│   └── index.js
├── ecosystem.config.js
├── Dockerfile
├── docker-compose.yml
├── nginx.conf
├── .env.example
├── .dockerignore
└── package.json

▶ Example: PM2 ecosystem.config.js

JAVASCRIPT 📖 Display only
module.exports = {
  apps: [
    {
      name: 'charlie-api',
      script: 'app.js',
      instances: 'max',
      exec_mode: 'cluster',
      autorestart: true,
      watch: false,
      max_memory_restart: '512M',
      env_development: {
        NODE_ENV: 'development',
        PORT: 3000,
      },
      env_production: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
      error_file: './logs/error.log',
      out_file: './logs/out.log',
      merge_logs: true,
      log_date_format: 'YYYY-MM-DD HH:mm:ss',
      max_restarts: 10,
      restart_delay: 4000,
      kill_timeout: 5000,
      listen_timeout: 10000,
    },
  ],
  deploy: {
    production: {
      user: 'deploy',
      host: 'api.example.com',
      ref: 'origin/main',
      repo: 'git@github.com:charlie/api.git',
      path: '/var/www/charlie-api',
      'pre-deploy-local': '',
      'post-deploy':
        'npm ci && pm2 reload ecosystem.config.js --env production',
      'pre-setup': '',
    },
  },
};
42 logic lines (exceeds 40-line limit, display only)

▶ Example: Dockerfile (Optimized for Multi-Stage Builds)

DOCKERFILE
FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build 2>/dev/null || true

FROM node:20-alpine AS production

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY --from=builder /app ./

RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
USER nodejs

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD node healthcheck.js

CMD ["node", "app.js"]

▶ Example: .env.example

TEXT 📖 Display only
NODE_ENV=production
PORT=3000
MONGO_URI=mongodb://mongo:27017/charlie-api
REDIS_URL=redis://redis:6379
JWT_SECRET=your-secret-key-here
LOG_LEVEL=info
CORS_ORIGIN=https://example.com
RATE_LIMIT_WINDOW=60000
RATE_LIMIT_MAX=100

▶ Example: healthcheck.js

JAVASCRIPT
const http = require('http');

const req = http.request(
  {
    hostname: '127.0.0.1',
    port: parseInt(process.env.PORT, 10) || 3000,
    path: '/healthz',
    timeout: 2000,
  },
  (res) => {
    process.exit(res.statusCode === 200 ? 0 : 1);
  }
);

req.on('error', () => process.exit(1));
req.on('timeout', () => { req.destroy(); process.exit(1); });
req.end();
▶ Try it Yourself

▶ Example: Health check endpoint in app.js

JAVASCRIPT 📖 Display only
const express = require('express');
const mongoose = require('mongoose');
const config = require('./config');

const app = express();
app.use(express.json());

app.get('/healthz', (req, res) => {
  res.json({ status: 'alive', pid: process.pid });
});

app.get('/readyz', async (req, res) => {
  try {
    await mongoose.connection.db.admin().ping();
    res.json({ status: 'ready', pid: process.pid, uptime: process.uptime() });
  } catch {
    res.status(503).json({ status: 'not ready', pid: process.pid });
  }
});

app.get('/health', async (req, res) => {
  const checks = {
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    memory: process.memoryUsage(),
    pid: process.pid,
  };

  try {
    await mongoose.connection.db.admin().ping();
    checks.database = 'ok';
  } catch {
    checks.database = 'error';
    checks.status = 'degraded';
  }

  const isHealthy = checks.database === 'ok';
  res.status(isHealthy ? 200 : 503).json(checks);
});

app.get('/api/users', async (req, res) => {
  console.time('fetch-users');
  const users = await mongoose.model('User').find().lean();
  console.timeEnd('fetch-users');
  res.json({ success: true, data: users });
});

mongoose.connect(config.mongoUri).then(() => {
  app.listen(config.port, () => {
    console.log(`Server running on port ${config.port} [${config.env}]`);
  });
});

process.on('SIGTERM', () => {
  console.log('SIGTERM received, shutting down gracefully...');
  mongoose.connection.close();
  process.exit(0);
});

process.on('SIGINT', () => {
  console.log('SIGINT received, shutting down gracefully...');
  mongoose.connection.close();
  process.exit(0);
});
55 logic lines (exceeds 40-line limit, display only)

❓ FAQ

Q Can Docker and PM2 be used together?
A Yes, but typically you choose one or the other. Node.js runs directly inside a Docker container, with restarts managed by K8s or Docker Compose; PM2 is suitable for non-containerized deployments.
Q How do I choose a cloud service platform?
A For small projects, use Vercel or Railway (zero configuration); for medium-sized projects, use AWS or GCP (flexible but complex); for projects in China, use Alibaba Cloud or Tencent Cloud. Choose based on your budget and your team’s experience.
Q What are some commonly used CI/CD tools?
A GitHub Actions (integrates well with GitHub), GitLab CI (convenient for self-hosting), Jenkins (enterprise-grade, with many plugins), and CircleCI (cloud-native).
Q How do I monitor a Node.js application in a production environment?
A Use PM2, New Relic, Datadog, or a self-hosted Prometheus + Grafana setup to monitor CPU, memory, response time, and error rates.
Q How do I perform a health check after deployment?
A Add the /health endpoint, which returns { status: 'ok' }; Docker HEALTHCHECK or K8s livenessProbe periodically accesses this endpoint.

Q: Which should I use, PM2 or Docker? A: The two are not mutually exclusive. Docker addresses environment consistency issues, while PM2 handles process management and clustering. For production environments, we recommend using Docker and PM2 together, with PM2 running inside a Docker container to manage multiple processes.

Q: How can I achieve zero-downtime deployment? A: Replace pm2 restart with pm2 reload. PM2 will restart the Workers one by one, ensuring that some instances remain online to handle requests at all times. In a Docker environment, you can use blue-green deployment or rolling update strategies.

Q: What are the limitations of cluster mode? A: In cluster mode, processes do not share memory; sessions must be shared using external storage such as Redis; WebSockets must be used in conjunction with sticky sessions or Pub/Sub message synchronization; and the file system cache is independent for each process.

Q: How do I monitor the production environment? A: PM2 offers pm2 monit real-time monitoring and pm2 plus an online dashboard; commercial solutions include New Relic, Datadog, and Prometheus + Grafana; key metrics include CPU, memory, event loop latency, and request response time.

Q: How can I reduce the size of a Docker image? A: Use the node:alpine base image, use multi-stage builds to separate compilation dependencies, .dockerignore exclude unnecessary files, npm ci --only=production install only production dependencies, and clear the npm cache npm cache clean --force.

Q: What are the advantages of using Nginx as a reverse proxy? A: SSL offloading reduces the encryption overhead on Node.js; static resources are handled directly by Nginx; load balancing distributes requests; rate limiting prevents DDoS attacks; gzip compression reduces data transfer volume; and caching speeds up responses.


📖 Summary

(1) Key Points of This Lesson

(2) Homework

  1. Start your Express project using PM2, configure ecosystem.config.js, enable cluster mode, and use pm2 monit to monitor the running status.
  2. Write a Dockerfile for the project, use multi-stage builds to optimize the image size, and use docker-compose to start Node.js and MongoDB simultaneously
  3. Implement the three health check endpoints /health, /healthz, and /readyz, and configure HEALTHCHECK in Docker.
  4. Use autocannon to perform load testing on your API, comparing the throughput differences between single-process and cluster modes.
  5. Write the Nginx reverse proxy configuration to implement SSL termination and load balancing, and use ab or curl to verify that the proxy forwarding is working properly.

📝 Exercises

  1. Complete all the code examples in this lesson and make sure each one runs correctly.
  2. Modify the comprehensive example and add your own extensions
  3. Review the official documentation, identify 1–2 APIs not covered in this lesson, and write test code for them.
  4. Reflection: How would you apply what you’ve learned in this lesson to a real-world project?
  5. Try to combine what you’ve learned in this lesson with material from previous lessons to build a small project.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏