Node.js Tutorial
Learn Node.js from fundamentals to advanced concepts, including modules, asynchronous programming, APIs, and production deployment.
Overview
Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine that allows you to run JavaScript on the server side. It's designed for building scalable network applications with an event-driven, non-blocking I/O model.
Getting Started
Installation
bash
# Using Node Version Manager (recommended)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install node
nvm use node
# Direct installation
# Download from https://nodejs.org
# Using package managers
# Ubuntu/Debian
sudo apt update
sudo apt install nodejs npm
# macOS (Homebrew)
brew install node
# Verify installation
node --version
npm --version
First Node.js Application
javascript
// hello.js
console.log('Hello, Node.js!')
// Run the application
// node hello.js
Interactive REPL
bash
# Start Node.js REPL
node
# In REPL
> console.log('Hello World')
> 2 + 3
> const name = 'Node.js'
> name
> .exit // Exit REPL
Core Concepts
Event Loop
javascript
// Event loop demonstration
console.log('Start')
setTimeout(() => {
console.log('Timeout callback')
}, 0)
setImmediate(() => {
console.log('Immediate callback')
})
process.nextTick(() => {
console.log('Next tick callback')
})
console.log('End')
// Output order:
// Start
// End
// Next tick callback
// Immediate callback
// Timeout callback
Global Objects
javascript
// Global objects available in Node.js
console.log(__dirname) // Current directory
console.log(__filename) // Current file path
console.log(process.cwd()) // Current working directory
console.log(process.env) // Environment variables
console.log(process.argv) // Command line arguments
// Global functions
setTimeout(() => {}, 1000)
setInterval(() => {}, 1000)
setImmediate(() => {})
clearTimeout()
clearInterval()
clearImmediate()
Process Object
javascript
// Process information
console.log('Node.js version:', process.version)
console.log('Platform:', process.platform)
console.log('Architecture:', process.arch)
console.log('Memory usage:', process.memoryUsage())
console.log('Uptime:', process.uptime())
// Command line arguments
process.argv.forEach((arg, index) => {
console.log(`${index}: ${arg}`)
})
// Environment variables
console.log('NODE_ENV:', process.env.NODE_ENV)
console.log('PORT:', process.env.PORT)
// Exit process
process.exit(0) // Success
process.exit(1) // Error
Modules System
CommonJS Modules
javascript
// math.js - Creating a module
const add = (a, b) => a + b
const subtract = (a, b) => a - b
const multiply = (a, b) => a * b
// Export methods
module.exports = {
add,
subtract,
multiply
}
// Alternative export syntax
exports.divide = (a, b) => a / b
// app.js - Using the module
const math = require('./math')
const { add, subtract } = require('./math')
console.log(math.add(5, 3)) // 8
console.log(subtract(10, 4)) // 6
ES6 Modules
javascript
// math.mjs - ES6 module
export const add = (a, b) => a + b
export const subtract = (a, b) => a - b
export default function multiply(a, b) {
return a * b
}
// app.mjs - Using ES6 modules
import multiply, { add, subtract } from './math.mjs'
import * as math from './math.mjs'
console.log(add(5, 3))
console.log(multiply(4, 2))
// package.json configuration for ES6 modules
{
"type": "module"
}
Built-in Modules
javascript
// File System
const fs = require('fs')
const path = require('path')
// HTTP
const http = require('http')
const https = require('https')
// URL and Query String
const url = require('url')
const querystring = require('querystring')
// Crypto
const crypto = require('crypto')
// OS
const os = require('os')
// Events
const EventEmitter = require('events')
// Stream
const { Readable, Writable, Transform } = require('stream')
File System Operations
Reading Files
javascript
const fs = require('fs')
const path = require('path')
// Synchronous reading
try {
const data = fs.readFileSync('file.txt', 'utf8')
console.log(data)
} catch (error) {
console.error('Error reading file:', error.message)
}
// Asynchronous reading (callback)
fs.readFile('file.txt', 'utf8', (error, data) => {
if (error) {
console.error('Error reading file:', error.message)
return
}
console.log(data)
})
// Asynchronous reading (promises)
const fsPromises = require('fs').promises
async function readFileAsync() {
try {
const data = await fsPromises.readFile('file.txt', 'utf8')
console.log(data)
} catch (error) {
console.error('Error reading file:', error.message)
}
}
readFileAsync()
Writing Files
javascript
const fs = require('fs')
// Synchronous writing
try {
fs.writeFileSync('output.txt', 'Hello, Node.js!', 'utf8')
console.log('File written successfully')
} catch (error) {
console.error('Error writing file:', error.message)
}
// Asynchronous writing
fs.writeFile('output.txt', 'Hello, Node.js!', 'utf8', (error) => {
if (error) {
console.error('Error writing file:', error.message)
return
}
console.log('File written successfully')
})
// Append to file
fs.appendFile('log.txt', 'New log entry\n', (error) => {
if (error) {
console.error('Error appending to file:', error.message)
return
}
console.log('Data appended successfully')
})
Directory Operations
javascript
const fs = require('fs')
const path = require('path')
// Create directory
fs.mkdir('new-directory', { recursive: true }, (error) => {
if (error) {
console.error('Error creating directory:', error.message)
return
}
console.log('Directory created successfully')
})
// Read directory
fs.readdir('.', (error, files) => {
if (error) {
console.error('Error reading directory:', error.message)
return
}
console.log('Files:', files)
})
// Get file stats
fs.stat('file.txt', (error, stats) => {
if (error) {
console.error('Error getting file stats:', error.message)
return
}
console.log('Is file:', stats.isFile())
console.log('Is directory:', stats.isDirectory())
console.log('File size:', stats.size)
console.log('Created:', stats.birthtime)
console.log('Modified:', stats.mtime)
})
// Watch for file changes
fs.watchFile('file.txt', (current, previous) => {
console.log('File changed')
console.log('Current:', current.mtime)
console.log('Previous:', previous.mtime)
})
HTTP Server and Client
Basic HTTP Server
javascript
const http = require('http')
const url = require('url')
const server = http.createServer((request, response) => {
const parsedUrl = url.parse(request.url, true)
const path = parsedUrl.pathname
const method = request.method
// Set response headers
response.setHeader('Content-Type', 'application/json')
response.setHeader('Access-Control-Allow-Origin', '*')
// Handle different routes
if (path === '/' && method === 'GET') {
response.statusCode = 200
response.end(JSON.stringify({ message: 'Hello, World!' }))
} else if (path === '/users' && method === 'GET') {
response.statusCode = 200
response.end(JSON.stringify({ users: ['Alice', 'Bob', 'Charlie'] }))
} else {
response.statusCode = 404
response.end(JSON.stringify({ error: 'Not Found' }))
}
})
const PORT = process.env.PORT || 3000
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})
// Handle server errors
server.on('error', (error) => {
console.error('Server error:', error.message)
})
Handling POST Requests
javascript
const http = require('http')
const server = http.createServer((request, response) => {
if (request.method === 'POST') {
let body = ''
// Collect data chunks
request.on('data', (chunk) => {
body += chunk.toString()
})
// Process complete data
request.on('end', () => {
try {
const data = JSON.parse(body)
console.log('Received data:', data)
response.statusCode = 200
response.setHeader('Content-Type', 'application/json')
response.end(JSON.stringify({
message: 'Data received successfully',
received: data
}))
} catch (error) {
response.statusCode = 400
response.end(JSON.stringify({ error: 'Invalid JSON' }))
}
})
} else {
response.statusCode = 405
response.end(JSON.stringify({ error: 'Method Not Allowed' }))
}
})
server.listen(3000, () => {
console.log('Server running on port 3000')
})
HTTP Client
javascript
const http = require('http')
const https = require('https')
// GET request
const options = {
hostname: 'jsonplaceholder.typicode.com',
port: 443,
path: '/posts/1',
method: 'GET'
}
const request = https.request(options, (response) => {
let data = ''
response.on('data', (chunk) => {
data += chunk
})
response.on('end', () => {
console.log('Response:', JSON.parse(data))
})
})
request.on('error', (error) => {
console.error('Request error:', error.message)
})
request.end()
// POST request
const postData = JSON.stringify({
title: 'New Post',
body: 'This is a new post',
userId: 1
})
const postOptions = {
hostname: 'jsonplaceholder.typicode.com',
port: 443,
path: '/posts',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData)
}
}
const postRequest = https.request(postOptions, (response) => {
let data = ''
response.on('data', (chunk) => {
data += chunk
})
response.on('end', () => {
console.log('POST Response:', JSON.parse(data))
})
})
postRequest.on('error', (error) => {
console.error('POST Request error:', error.message)
})
postRequest.write(postData)
postRequest.end()
Asynchronous Programming
Callbacks
javascript
// Callback pattern
function fetchData(callback) {
setTimeout(() => {
const data = { id: 1, name: 'John Doe' }
callback(null, data) // null for error, data as result
}, 1000)
}
fetchData((error, data) => {
if (error) {
console.error('Error:', error)
return
}
console.log('Data:', data)
})
// Callback hell example
getData((error, data) => {
if (error) {
console.error(error)
return
}
processData(data, (error, processedData) => {
if (error) {
console.error(error)
return
}
saveData(processedData, (error, result) => {
if (error) {
console.error(error)
return
}
console.log('Success:', result)
})
})
})
Promises
javascript
// Creating promises
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5
if (success) {
resolve({ id: 1, name: 'John Doe' })
} else {
reject(new Error('Failed to fetch data'))
}
}, 1000)
})
}
// Using promises
fetchData()
.then(data => {
console.log('Data:', data)
return processData(data)
})
.then(processedData => {
console.log('Processed:', processedData)
return saveData(processedData)
})
.then(result => {
console.log('Saved:', result)
})
.catch(error => {
console.error('Error:', error.message)
})
.finally(() => {
console.log('Operation completed')
})
// Promise utilities
Promise.all([fetchData(), fetchData(), fetchData()])
.then(results => {
console.log('All results:', results)
})
.catch(error => {
console.error('One or more promises failed:', error)
})
Promise.race([fetchData(), fetchData()])
.then(result => {
console.log('First result:', result)
})
.catch(error => {
console.error('First error:', error)
})
Async/Await
javascript
// Async function
async function fetchUserData(userId) {
try {
const user = await fetchUser(userId)
const posts = await fetchUserPosts(userId)
const comments = await fetchUserComments(userId)
return {
user,
posts,
comments
}
} catch (error) {
console.error('Error fetching user data:', error.message)
throw error
}
}
// Using async/await
async function main() {
try {
const userData = await fetchUserData(1)
console.log('User data:', userData)
} catch (error) {
console.error('Failed to get user data:', error.message)
}
}
main()
// Parallel execution with async/await
async function fetchAllData() {
try {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments()
])
return { users, posts, comments }
} catch (error) {
console.error('Error fetching data:', error.message)
throw error
}
}
Events and EventEmitter
Basic EventEmitter
javascript
const EventEmitter = require('events')
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter()
// Register event listeners
myEmitter.on('event', (data) => {
console.log('Event received:', data)
})
myEmitter.on('error', (error) => {
console.error('Error occurred:', error.message)
})
// Emit events
myEmitter.emit('event', { message: 'Hello, Events!' })
myEmitter.emit('error', new Error('Something went wrong'))
// One-time listener
myEmitter.once('startup', () => {
console.log('Application started')
})
myEmitter.emit('startup') // Will trigger
myEmitter.emit('startup') // Will not trigger
// Remove listeners
const listener = (data) => console.log('Listener:', data)
myEmitter.on('test', listener)
myEmitter.removeListener('test', listener)
myEmitter.removeAllListeners('test')
Custom EventEmitter
javascript
const EventEmitter = require('events')
class Logger extends EventEmitter {
log(message) {
console.log(`[${new Date().toISOString()}] ${message}`)
this.emit('logged', { message, timestamp: new Date() })
}
error(message) {
console.error(`[${new Date().toISOString()}] ERROR: ${message}`)
this.emit('error', { message, timestamp: new Date() })
}
}
const logger = new Logger()
logger.on('logged', (data) => {
console.log('Log event:', data)
})
logger.on('error', (data) => {
console.error('Error event:', data)
})
logger.log('Application started')
logger.error('Database connection failed')
Streams
Readable Streams
javascript
const { Readable } = require('stream')
const fs = require('fs')
// File stream
const readStream = fs.createReadStream('large-file.txt', {
encoding: 'utf8',
highWaterMark: 1024 // Buffer size
})
readStream.on('data', (chunk) => {
console.log('Received chunk:', chunk.length, 'bytes')
})
readStream.on('end', () => {
console.log('Stream ended')
})
readStream.on('error', (error) => {
console.error('Stream error:', error.message)
})
// Custom readable stream
class NumberStream extends Readable {
constructor(options) {
super(options)
this.current = 0
this.max = 10
}
_read() {
if (this.current < this.max) {
this.push(`Number: ${this.current}\n`)
this.current++
} else {
this.push(null) // End stream
}
}
}
const numberStream = new NumberStream()
numberStream.on('data', (chunk) => {
console.log(chunk.toString())
})
Writable Streams
javascript
const { Writable } = require('stream')
const fs = require('fs')
// File write stream
const writeStream = fs.createWriteStream('output.txt')
writeStream.write('Hello, ')
writeStream.write('Node.js!')
writeStream.end()
writeStream.on('finish', () => {
console.log('Write completed')
})
// Custom writable stream
class ConsoleStream extends Writable {
_write(chunk, encoding, callback) {
console.log(`Writing: ${chunk.toString().toUpperCase()}`)
callback()
}
}
const consoleStream = new ConsoleStream()
consoleStream.write('hello world')
consoleStream.end()
Transform Streams
javascript
const { Transform } = require('stream')
// Custom transform stream
class UpperCaseTransform extends Transform {
_transform(chunk, encoding, callback) {
const upperChunk = chunk.toString().toUpperCase()
callback(null, upperChunk)
}
}
const upperTransform = new UpperCaseTransform()
// Pipe streams together
process.stdin
.pipe(upperTransform)
.pipe(process.stdout)
// File processing pipeline
const fs = require('fs')
fs.createReadStream('input.txt')
.pipe(new UpperCaseTransform())
.pipe(fs.createWriteStream('output.txt'))
NPM and Package Management
Package.json
json
{
"name": "my-node-app",
"version": "1.0.0",
"description": "A sample Node.js application",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "jest",
"build": "webpack --mode production",
"lint": "eslint src/",
"format": "prettier --write src/"
},
"keywords": ["node", "javascript", "api"],
"author": "Your Name <your.email@example.com>",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.0.0",
"dotenv": "^16.0.0"
},
"devDependencies": {
"nodemon": "^2.0.20",
"jest": "^29.0.0",
"eslint": "^8.0.0",
"prettier": "^2.8.0"
},
"engines": {
"node": ">=16.0.0",
"npm": ">=8.0.0"
}
}
NPM Commands
bash
# Initialize new project
npm init
npm init -y # Skip questions
# Install packages
npm install express
npm install --save express # Same as above
npm install --save-dev nodemon # Development dependency
npm install -g nodemon # Global installation
# Install from package.json
npm install
npm ci # Clean install (faster, for CI/CD)
# Update packages
npm update
npm update express
npm outdated # Check for outdated packages
# Remove packages
npm uninstall express
npm uninstall --save-dev nodemon
# List packages
npm list
npm list --depth=0 # Top-level only
npm list -g # Global packages
# Run scripts
npm start
npm run dev
npm test
# Publish package
npm login
npm publish
npm version patch # Increment version
Environment Variables
javascript
// .env file
NODE_ENV=development
PORT=3000
DATABASE_URL=mongodb://localhost:27017/myapp
JWT_SECRET=your-secret-key
API_KEY=your-api-key
// Load environment variables
require('dotenv').config()
// Use environment variables
const port = process.env.PORT || 3000
const dbUrl = process.env.DATABASE_URL
const jwtSecret = process.env.JWT_SECRET
console.log('Environment:', process.env.NODE_ENV)
console.log('Port:', port)
Error Handling
Try-Catch with Async/Await
javascript
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const userData = await response.json()
return userData
} catch (error) {
console.error('Error fetching user data:', error.message)
throw error // Re-throw if needed
}
}
// Usage
async function main() {
try {
const user = await fetchUserData(123)
console.log('User:', user)
} catch (error) {
console.error('Failed to get user:', error.message)
}
}
Global Error Handling
javascript
// Uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error)
process.exit(1)
})
// Unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason)
process.exit(1)
})
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully')
server.close(() => {
console.log('Process terminated')
})
})
process.on('SIGINT', () => {
console.log('SIGINT received, shutting down gracefully')
process.exit(0)
})
Custom Error Classes
javascript
class ValidationError extends Error {
constructor(message, field) {
super(message)
this.name = 'ValidationError'
this.field = field
this.statusCode = 400
}
}
class NotFoundError extends Error {
constructor(resource) {
super(`${resource} not found`)
this.name = 'NotFoundError'
this.statusCode = 404
}
}
// Usage
function validateUser(user) {
if (!user.email) {
throw new ValidationError('Email is required', 'email')
}
if (!user.name) {
throw new ValidationError('Name is required', 'name')
}
}
try {
validateUser({ name: 'John' })
} catch (error) {
if (error instanceof ValidationError) {
console.error(`Validation error in ${error.field}: ${error.message}`)
} else {
console.error('Unexpected error:', error.message)
}
}
Testing
Unit Testing with Jest
javascript
// math.js
function add(a, b) {
return a + b
}
function subtract(a, b) {
return a - b
}
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero')
}
return a / b
}
module.exports = { add, subtract, divide }
// math.test.js
const { add, subtract, divide } = require('./math')
describe('Math functions', () => {
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3)
})
test('subtracts 5 - 3 to equal 2', () => {
expect(subtract(5, 3)).toBe(2)
})
test('divides 10 / 2 to equal 5', () => {
expect(divide(10, 2)).toBe(5)
})
test('throws error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero')
})
})
// Async testing
describe('Async functions', () => {
test('async function resolves', async () => {
const result = await fetchData()
expect(result).toHaveProperty('id')
})
test('promise resolves', () => {
return fetchData().then(data => {
expect(data).toHaveProperty('id')
})
})
})
Mocking
javascript
// userService.js
const axios = require('axios')
async function getUser(id) {
const response = await axios.get(`/api/users/${id}`)
return response.data
}
module.exports = { getUser }
// userService.test.js
const axios = require('axios')
const { getUser } = require('./userService')
jest.mock('axios')
const mockedAxios = axios
describe('User Service', () => {
test('should fetch user', async () => {
const userData = { id: 1, name: 'John Doe' }
mockedAxios.get.mockResolvedValue({ data: userData })
const result = await getUser(1)
expect(mockedAxios.get).toHaveBeenCalledWith('/api/users/1')
expect(result).toEqual(userData)
})
})
Performance and Optimization
Clustering
javascript
const cluster = require('cluster')
const http = require('http')
const numCPUs = require('os').cpus().length
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`)
// Fork workers
for (let i = 0; i < numCPUs; i++) {
cluster.fork()
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died`)
cluster.fork() // Restart worker
})
} else {
// Worker process
const server = http.createServer((req, res) => {
res.writeHead(200)
res.end(`Hello from worker ${process.pid}`)
})
server.listen(3000, () => {
console.log(`Worker ${process.pid} started`)
})
}
Memory Management
javascript
// Monitor memory usage
function logMemoryUsage() {
const usage = process.memoryUsage()
console.log({
rss: `${Math.round(usage.rss / 1024 / 1024)} MB`,
heapTotal: `${Math.round(usage.heapTotal / 1024 / 1024)} MB`,
heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)} MB`,
external: `${Math.round(usage.external / 1024 / 1024)} MB`
})
}
setInterval(logMemoryUsage, 5000)
// Force garbage collection (for debugging)
if (global.gc) {
global.gc()
} else {
console.log('Garbage collection unavailable. Pass --expose-gc when launching node.')
}
Profiling
javascript
// CPU profiling
const fs = require('fs')
// Start profiling
const profiler = require('v8-profiler-next')
profiler.startProfiling('CPU profile')
// Your application code here
setTimeout(() => {
// Stop profiling
const profile = profiler.stopProfiling('CPU profile')
profile.export((error, result) => {
fs.writeFileSync('profile.cpuprofile', result)
profile.delete()
console.log('Profile saved to profile.cpuprofile')
})
}, 10000)
Security Best Practices
Input Validation
javascript
const validator = require('validator')
function validateUserInput(data) {
const errors = []
// Email validation
if (!data.email || !validator.isEmail(data.email)) {
errors.push('Valid email is required')
}
// Password validation
if (!data.password || data.password.length < 8) {
errors.push('Password must be at least 8 characters')
}
// Sanitize input
const sanitizedData = {
email: validator.normalizeEmail(data.email),
name: validator.escape(data.name),
age: validator.toInt(data.age)
}
return { errors, sanitizedData }
}
Environment Security
javascript
// Secure environment configuration
const config = {
port: process.env.PORT || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
// Database
dbUrl: process.env.DATABASE_URL || 'mongodb://localhost:27017/app',
// JWT
jwtSecret: process.env.JWT_SECRET,
jwtExpiry: process.env.JWT_EXPIRY || '1h',
// API keys (never log these)
apiKey: process.env.API_KEY,
// Security headers
corsOrigin: process.env.CORS_ORIGIN || 'http://localhost:3000'
}
// Validate required environment variables
const requiredEnvVars = ['JWT_SECRET', 'DATABASE_URL']
const missingEnvVars = requiredEnvVars.filter(envVar => !process.env[envVar])
if (missingEnvVars.length > 0) {
console.error('Missing required environment variables:', missingEnvVars)
process.exit(1)
}
module.exports = config
Production Deployment
Process Management with PM2
bash
# Install PM2
npm install -g pm2
# Start application
pm2 start app.js
pm2 start app.js --name "my-app"
# Start with cluster mode
pm2 start app.js -i max # Use all CPU cores
pm2 start app.js -i 4 # Use 4 instances
# Process management
pm2 list
pm2 stop my-app
pm2 restart my-app
pm2 delete my-app
# Monitoring
pm2 monit
pm2 logs
pm2 logs my-app
# Auto-restart on file changes
pm2 start app.js --watch
# Startup script
pm2 startup
pm2 save
PM2 Ecosystem File
javascript
// ecosystem.config.js
module.exports = {
apps: [{
name: 'my-app',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 8080
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log',
time: true
}]
}
// Start with ecosystem file
// pm2 start ecosystem.config.js
// pm2 start ecosystem.config.js --env production
Docker Deployment
dockerfile
# Dockerfile
FROM node:18-alpine
# Create app directory
WORKDIR /usr/src/app
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodeuser -u 1001
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production && npm cache clean --force
# Copy app source
COPY --chown=nodeuser:nodejs . .
# Switch to non-root user
USER nodeuser
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# Start application
CMD ["node", "app.js"]
Logging in Production
javascript
const winston = require('winston')
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'my-app' },
transports: [
new winston.transports.File({
filename: 'logs/error.log',
level: 'error'
}),
new winston.transports.File({
filename: 'logs/combined.log'
})
]
})
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}))
}
// Usage
logger.info('Application started', { port: 3000 })
logger.error('Database connection failed', { error: error.message })
logger.warn('High memory usage detected', { usage: process.memoryUsage() })
module.exports = logger
This comprehensive Node.js tutorial covers server-side JavaScript development from fundamentals to production deployment. Practice building real applications to master these concepts.