Prerequisites
- Basic understanding of JavaScript 📝
- TypeScript installation ⚡
- VS Code or preferred IDE 💻
What you'll learn
- Understand load testing fundamentals 🎯
- Apply Artillery and K6 in real projects 🏗️
- Debug common testing issues 🐛
- Write type-safe load testing code ✨
🎯 Introduction
Welcome to the thrilling world of load testing! 🎉 In this guide, we’ll explore how to stress-test your applications using Artillery and K6 with TypeScript.
You’ll discover how load testing can save your application from crashing when millions of users suddenly decide they love your app! 📈 Whether you’re building APIs 🌐, microservices 🖥️, or full-stack applications 📱, understanding load testing is essential for building robust, scalable systems.
By the end of this tutorial, you’ll feel confident load testing like a performance engineer! Let’s dive in! 🏊♂️
📚 Understanding Load Testing
🤔 What is Load Testing?
Load testing is like hosting a massive party 🎉 for your application! Think of it as inviting thousands of virtual guests to your API endpoints simultaneously to see if your server can handle the crowd without collapsing.
In technical terms, load testing simulates real user behavior at scale to identify performance bottlenecks before they hit production 🎯. This means you can:
- ✨ Catch performance issues early
- 🚀 Optimize response times
- 🛡️ Prevent server crashes
- 📊 Plan capacity requirements
💡 Why Use Artillery and K6?
Here’s why developers love these tools:
- TypeScript Support 🔒: Write type-safe tests
- Real-world Scenarios 💻: Simulate actual user patterns
- Detailed Reporting 📖: Beautiful metrics and graphs
- CI/CD Integration 🔧: Automated performance testing
Real-world example: Imagine your e-commerce site during Black Friday 🛒. With load testing, you can simulate 50,000 shoppers clicking “Add to Cart” simultaneously!
🔧 Basic Syntax and Usage
📝 Setting Up Artillery
Let’s start with Artillery, a fantastic load testing toolkit:
# 🚀 Install Artillery with TypeScript support
npm install -D artillery artillery-plugin-typescript @types/node
// 👋 Hello, Artillery with TypeScript!
// artillery-config.ts
interface ArtilleryConfig {
config: {
target: string;
phases: Array<{
duration: number;
arrivalRate: number;
name?: string;
}>;
plugins?: {
typescript: boolean;
};
};
scenarios: Array<{
name: string;
weight: number;
flow: Array<{
get?: {
url: string;
headers?: Record<string, string>;
};
post?: {
url: string;
json: Record<string, any>;
};
}>;
}>;
}
// 🎨 Creating a simple load test config
const loadTestConfig: ArtilleryConfig = {
config: {
target: "https://api.mystore.com",
phases: [
{
duration: 60,
arrivalRate: 10,
name: "Warm up"
},
{
duration: 300,
arrivalRate: 50,
name: "Peak load"
}
],
plugins: {
typescript: true
}
},
scenarios: [
{
name: "Browse products",
weight: 70,
flow: [
{
get: {
url: "/api/products",
headers: {
"User-Agent": "LoadTest-Bot 🤖"
}
}
}
]
}
]
};
💡 Explanation: We define test phases (warm-up, peak load) and scenarios with different weights to simulate realistic user behavior!
🎯 Setting Up K6
Now let’s explore K6, another powerful tool:
# ⚡ Install K6 (requires separate K6 installation)
npm install -D @types/k6
// 🎮 K6 TypeScript test
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';
// 📊 Custom metrics
const errorRate = new Rate('errors');
// 🎯 Test configuration
export const options = {
vus: 50, // 👥 50 virtual users
duration: '2m', // ⏱️ Test for 2 minutes
thresholds: {
http_req_duration: ['p(95)<500'], // 🎯 95% under 500ms
'errors': ['rate<0.1'], // ❌ Error rate under 10%
},
};
// 🏪 E-commerce load test
export default function (): void {
// 🛒 Simulate user browsing products
const productResponse = http.get('https://api.mystore.com/api/products');
check(productResponse, {
'✅ Products loaded': (r) => r.status === 200,
'⚡ Response time OK': (r) => r.timings.duration < 1000,
});
errorRate.add(productResponse.status !== 200);
// 💤 Think time - users don't click instantly!
sleep(Math.random() * 3 + 1);
}
💡 Practical Examples
🛒 Example 1: E-commerce Load Testing
Let’s test an online store during a sale:
// 🏪 Complete e-commerce load test with TypeScript
interface Product {
id: string;
name: string;
price: number;
stock: number;
}
interface CartItem {
productId: string;
quantity: number;
}
interface LoadTestUser {
userId: string;
sessionId: string;
cart: CartItem[];
}
// 🎯 Artillery scenario for shopping flow
const ecommerceTest = {
config: {
target: "https://api.blackfridaystore.com",
phases: [
{
duration: 120,
arrivalRate: 25,
name: "🔥 Black Friday Rush"
}
]
},
scenarios: [
{
name: "🛍️ Complete Shopping Journey",
weight: 100,
flow: [
// 👀 Browse products
{
get: {
url: "/api/products?category=electronics",
capture: {
json: "$.products[*].id",
as: "productIds"
}
}
},
// 🛒 Add to cart
{
post: {
url: "/api/cart/add",
json: {
productId: "{{ productIds[0] }}",
quantity: 1
}
}
},
// 💳 Checkout process
{
post: {
url: "/api/checkout",
json: {
paymentMethod: "credit_card",
shippingAddress: {
street: "123 Test St",
city: "Load Test City"
}
}
}
}
]
}
]
};
console.log("🚀 Starting Black Friday load test!");
🎯 Try it yourself: Add a “wishlist” scenario and test different product categories!
🎮 Example 2: Gaming API Load Test
Let’s stress-test a multiplayer game backend:
// 🎮 Gaming server load test with K6
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Counter, Trend } from 'k6/metrics';
// 📊 Game-specific metrics
const gameActions = new Counter('game_actions_total');
const leaderboardLatency = new Trend('leaderboard_response_time');
// 🏆 Player data types
interface Player {
id: string;
username: string;
level: number;
score: number;
}
interface GameSession {
sessionId: string;
players: Player[];
gameMode: 'battle_royale' | 'team_deathmatch' | 'survival';
}
export const options = {
scenarios: {
// 🎯 Simulate different player activities
casual_players: {
executor: 'constant-vus',
vus: 30,
duration: '5m',
tags: { player_type: 'casual' },
},
hardcore_gamers: {
executor: 'ramping-vus',
startVUs: 10,
stages: [
{ duration: '2m', target: 50 },
{ duration: '5m', target: 100 },
{ duration: '2m', target: 0 },
],
tags: { player_type: 'hardcore' },
},
},
thresholds: {
http_req_duration: ['p(95)<200'], // 🎯 Gaming needs fast responses!
'leaderboard_response_time': ['p(90)<100'],
},
};
export default function (): void {
const baseUrl = 'https://api.epicgame.com';
// 🎮 Player joins game
const joinResponse = http.post(`${baseUrl}/game/join`, {
gameMode: 'battle_royale',
region: 'us-east-1'
});
check(joinResponse, {
'🎯 Joined game successfully': (r) => r.status === 200,
'⚡ Join time acceptable': (r) => r.timings.duration < 500,
});
if (joinResponse.status === 200) {
const sessionData = JSON.parse(joinResponse.body as string) as GameSession;
// 🏃♂️ Simulate gameplay actions
for (let action = 0; action < 10; action++) {
const actionResponse = http.post(`${baseUrl}/game/action`, {
sessionId: sessionData.sessionId,
action: 'move',
x: Math.random() * 1000,
y: Math.random() * 1000
});
gameActions.add(1);
sleep(0.1); // 100ms between actions
}
// 📊 Check leaderboard (expensive operation)
const leaderboardStart = Date.now();
const leaderboard = http.get(`${baseUrl}/leaderboard/top100`);
leaderboardLatency.add(Date.now() - leaderboardStart);
check(leaderboard, {
'🏆 Leaderboard loaded': (r) => r.status === 200,
});
}
// 💤 Think time between game sessions
sleep(Math.random() * 5 + 2);
}
🚀 Advanced Concepts
🧙♂️ Advanced Topic 1: Custom Load Test Patterns
When you’re ready to level up, try these advanced patterns:
// 🎯 Advanced load testing with custom scenarios
interface LoadTestPattern {
name: string;
pattern: 'spike' | 'stress' | 'volume' | 'endurance';
execute: () => Promise<void>;
}
class AdvancedLoadTester {
private patterns: Map<string, LoadTestPattern> = new Map();
// 🪄 Register custom test patterns
addPattern(pattern: LoadTestPattern): void {
this.patterns.set(pattern.name, pattern);
console.log(`✨ Added pattern: ${pattern.name}`);
}
// 🎬 Execute test scenarios
async runPattern(name: string): Promise<void> {
const pattern = this.patterns.get(name);
if (!pattern) {
throw new Error(`❌ Pattern '${name}' not found!`);
}
console.log(`🚀 Running ${pattern.name} (${pattern.pattern}) pattern...`);
await pattern.execute();
console.log(`✅ Completed ${pattern.name}!`);
}
}
// 🌊 Spike testing pattern
const spikeTestPattern: LoadTestPattern = {
name: "Black Friday Spike",
pattern: 'spike',
execute: async () => {
// Suddenly increase load by 1000% for 2 minutes
console.log("📈 Simulating viral social media post effect!");
}
};
🏗️ Advanced Topic 2: Distributed Load Testing
For the brave performance engineers:
// 🚀 Distributed load testing coordination
type TestRegion = 'us-east' | 'us-west' | 'eu-central' | 'asia-pacific';
interface DistributedTest {
regions: TestRegion[];
totalLoad: number;
distribution: Record<TestRegion, number>;
}
// 🌍 Global load distribution
const globalLoadTest: DistributedTest = {
regions: ['us-east', 'us-west', 'eu-central', 'asia-pacific'],
totalLoad: 10000,
distribution: {
'us-east': 4000, // 40% of traffic
'us-west': 2500, // 25% of traffic
'eu-central': 2000, // 20% of traffic
'asia-pacific': 1500 // 15% of traffic
}
};
// 🎯 Regional test coordination
class RegionalLoadTestCoordinator {
async coordinateGlobalTest(test: DistributedTest): Promise<void> {
const promises = test.regions.map(region =>
this.executeRegionalLoad(region, test.distribution[region])
);
console.log("🌍 Starting global distributed load test...");
await Promise.all(promises);
console.log("🎉 Global test completed!");
}
private async executeRegionalLoad(region: TestRegion, load: number): Promise<void> {
console.log(`🚀 ${region}: Starting ${load} virtual users`);
// Execute region-specific load testing logic
}
}
⚠️ Common Pitfalls and Solutions
😱 Pitfall 1: Testing Against Production
// ❌ Wrong way - Don't attack production!
const badConfig = {
target: "https://my-live-site.com", // 💥 This will crash your site!
phases: [{ duration: 300, arrivalRate: 1000 }]
};
// ✅ Correct way - Use staging environment!
const goodConfig = {
target: "https://staging.my-site.com", // 🛡️ Safe testing environment
phases: [{ duration: 300, arrivalRate: 1000 }]
};
// 🎯 Environment management
interface TestEnvironment {
name: 'staging' | 'load-test' | 'pre-prod';
url: string;
maxUsers: number;
safetyLimits: {
maxDuration: number;
maxRPS: number;
};
}
const safeEnvironments: TestEnvironment[] = [
{
name: 'staging',
url: 'https://staging.example.com',
maxUsers: 500,
safetyLimits: {
maxDuration: 600, // 10 minutes max
maxRPS: 100
}
}
];
🤯 Pitfall 2: Unrealistic Test Scenarios
// ❌ Unrealistic - No human clicks this fast!
function unrealisticTest(): void {
for (let i = 0; i < 1000; i++) {
http.get('/api/products');
// No delay = robot behavior!
}
}
// ✅ Realistic - Include human behavior patterns
function realisticUserBehavior(): void {
// 👀 Browse products page
http.get('/api/products');
sleep(Math.random() * 5 + 2); // 2-7 seconds thinking time
// 🛒 Maybe add item to cart (not every time!)
if (Math.random() > 0.7) {
http.post('/api/cart/add', { productId: '123', quantity: 1 });
sleep(Math.random() * 3 + 1); // Decision time
}
// 📱 Check notifications occasionally
if (Math.random() > 0.8) {
http.get('/api/notifications');
sleep(1);
}
}
🛠️ Best Practices
- 🎯 Start Small: Begin with low load and gradually increase
- 📝 Monitor Everything: CPU, memory, database connections
- 🛡️ Test Safely: Never test production without permission
- 🎨 Realistic Scenarios: Mimic actual user behavior patterns
- ✨ Automate Testing: Include load tests in your CI/CD pipeline
- 📊 Baseline Everything: Know your normal performance metrics
- 🔄 Test Regularly: Performance can degrade over time
🧪 Hands-On Exercise
🎯 Challenge: Build a Social Media Load Test
Create a comprehensive load test for a social media API:
📋 Requirements:
- ✅ User authentication flow
- 🏷️ Feed browsing with pagination
- 👥 Post creation and interactions (likes, comments)
- 📅 Real-time notification checking
- 🎨 Include realistic user behavior patterns!
🚀 Bonus Points:
- Add different user types (lurkers vs active posters)
- Implement weekend vs weekday traffic patterns
- Create a “viral post” spike scenario
💡 Solution
🔍 Click to see solution
// 🎯 Social media load test with TypeScript!
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Counter, Rate } from 'k6/metrics';
// 📊 Social media metrics
const postCreations = new Counter('posts_created');
const engagementRate = new Rate('user_engagement');
// 👤 User types and behaviors
interface SocialUser {
id: string;
username: string;
type: 'lurker' | 'casual' | 'influencer';
engagementRate: number;
}
export const options = {
scenarios: {
// 👀 Lurkers (80% of users, low activity)
lurkers: {
executor: 'constant-vus',
vus: 80,
duration: '10m',
tags: { user_type: 'lurker' },
},
// 👥 Casual users (15% of users, moderate activity)
casual_users: {
executor: 'constant-vus',
vus: 15,
duration: '10m',
tags: { user_type: 'casual' },
},
// 🌟 Influencers (5% of users, high activity)
influencers: {
executor: 'constant-vus',
vus: 5,
duration: '10m',
tags: { user_type: 'influencer' },
},
},
thresholds: {
http_req_duration: ['p(95)<2000'],
'user_engagement': ['rate>0.3'], // 30% engagement rate
},
};
export default function (): void {
const baseUrl = 'https://api.socialmedia.com';
const userType = __ENV.USER_TYPE || 'casual';
// 🔐 Authenticate user
const authResponse = http.post(`${baseUrl}/auth/login`, {
username: `testuser_${Math.floor(Math.random() * 10000)}`,
password: 'loadtest123'
});
check(authResponse, {
'✅ Login successful': (r) => r.status === 200,
});
if (authResponse.status !== 200) return;
const token = JSON.parse(authResponse.body as string).token;
const headers = { 'Authorization': `Bearer ${token}` };
// 📱 Browse feed (everyone does this)
const feedResponse = http.get(`${baseUrl}/feed?limit=20`, { headers });
check(feedResponse, {
'📰 Feed loaded': (r) => r.status === 200,
});
sleep(Math.random() * 10 + 5); // 5-15 seconds reading
// 🎯 User behavior based on type
const engagementProbability = getUserEngagementRate(userType);
if (Math.random() < engagementProbability) {
// 👍 Like some posts
for (let i = 0; i < Math.floor(Math.random() * 5); i++) {
http.post(`${baseUrl}/posts/${getRandomPostId()}/like`, {}, { headers });
sleep(Math.random() * 2 + 0.5);
}
engagementRate.add(1);
// 💬 Maybe comment (less likely)
if (Math.random() < 0.3) {
http.post(`${baseUrl}/posts/${getRandomPostId()}/comment`, {
text: getRandomComment()
}, { headers });
sleep(Math.random() * 5 + 2);
}
// 📝 Create post (even less likely, except influencers)
if (userType === 'influencer' || Math.random() < 0.1) {
const postResponse = http.post(`${baseUrl}/posts`, {
text: "Just load testing! 🚀 #typescript #k6testing",
hashtags: ['typescript', 'loadtesting', 'performance']
}, { headers });
check(postResponse, {
'📝 Post created': (r) => r.status === 201,
});
postCreations.add(1);
sleep(Math.random() * 10 + 5);
}
}
// 🔔 Check notifications occasionally
if (Math.random() < 0.4) {
http.get(`${baseUrl}/notifications`, { headers });
sleep(Math.random() * 3 + 1);
}
// 💤 Think time before next action
sleep(Math.random() * 30 + 10);
}
// 🎯 Helper functions
function getUserEngagementRate(userType: string): number {
switch (userType) {
case 'lurker': return 0.1; // 10% engagement
case 'casual': return 0.4; // 40% engagement
case 'influencer': return 0.8; // 80% engagement
default: return 0.3;
}
}
function getRandomPostId(): string {
return `post_${Math.floor(Math.random() * 1000)}`;
}
function getRandomComment(): string {
const comments = [
"Great post! 👍",
"Love this! ❤️",
"So true! 💯",
"Thanks for sharing! 🙏",
"Amazing content! 🚀"
];
return comments[Math.floor(Math.random() * comments.length)];
}
🎓 Key Takeaways
You’ve learned so much! Here’s what you can now do:
- ✅ Create load tests with Artillery and K6 💪
- ✅ Avoid common mistakes that crash servers 🛡️
- ✅ Apply realistic patterns in test scenarios 🎯
- ✅ Debug performance issues like a pro 🐛
- ✅ Build scalable applications with confidence! 🚀
Remember: Load testing is your safety net, not your enemy! It’s here to help you build rock-solid applications that can handle anything. 🤝
🤝 Next Steps
Congratulations! 🎉 You’ve mastered load testing with TypeScript!
Here’s what to do next:
- 💻 Practice with the social media exercise above
- 🏗️ Add load tests to your current projects
- 📚 Move on to our next tutorial: Performance Monitoring and APM
- 🌟 Share your load testing victories with the community!
Remember: Every performance expert was once a beginner who crashed their first server during testing. Keep testing, keep learning, and most importantly, test responsibly! 🚀
Happy load testing! 🎉🚀✨