Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand cron job fundamentals ๐ฏ
- Apply cron scheduling in real projects ๐๏ธ
- Debug common scheduling issues ๐
- Write type-safe scheduled tasks โจ
๐ฏ Introduction
Welcome to the world of automated task scheduling! ๐ In this guide, weโll explore how to create cron jobs in TypeScript that run tasks at specific times - like having a reliable robot assistant that never forgets to do important work! ๐ค
Youโll discover how cron jobs can transform your backend applications by automating repetitive tasks. Whether youโre building cleanup scripts ๐งน, sending reminder emails ๐ง, or generating daily reports ๐, understanding cron jobs is essential for creating robust, self-managing applications.
By the end of this tutorial, youโll feel confident scheduling tasks like a time-traveling wizard! Letโs dive in! ๐โโ๏ธ
๐ Understanding Cron Jobs
๐ค What are Cron Jobs?
Cron jobs are like having a super-punctual assistant ๐ who never sleeps and always remembers to do tasks exactly when you tell them to. Think of it as setting multiple alarms ๐ฑ on your phone, but instead of waking you up, they wake up your code to perform specific tasks!
In TypeScript terms, cron jobs allow you to schedule functions to run automatically at specific intervals โฐ. This means you can:
- โจ Automate database cleanups
- ๐ Send scheduled notifications
- ๐ก๏ธ Run security scans periodically
- ๐ Generate reports at specific times
๐ก Why Use Cron Jobs?
Hereโs why developers love scheduled tasks:
- Automation ๐ค: Set it once, runs forever
- Reliability โฐ: Never miss scheduled tasks
- Resource Management ๐ป: Run heavy tasks during off-peak hours
- User Experience ๐: Background tasks donโt interrupt users
Real-world example: Imagine running an e-commerce site ๐. With cron jobs, you can automatically send โcart abandonmentโ emails every hour, clean up expired sessions daily, and generate sales reports every morning!
๐ง Basic Syntax and Usage
๐ Cron Expression Format
Cron expressions look mysterious but theyโre actually simple! Letโs decode them:
// ๐จ Cron expression format: "minute hour day month day-of-week"
// โโโโโโโโโโโโโโ minute (0 - 59)
// โ โโโโโโโโโโโโโโ hour (0 - 23)
// โ โ โโโโโโโโโโโโโโ day of month (1 - 31)
// โ โ โ โโโโโโโโโโโโโโ month (1 - 12)
// โ โ โ โ โโโโโโโโโโโโโโ day of week (0 - 6) Sunday = 0
// โ โ โ โ โ
// * * * * *
// ๐ก Common examples:
"0 9 * * *" // ๐
Every day at 9:00 AM
"*/15 * * * *" // โฐ Every 15 minutes
"0 0 * * 0" // ๐๏ธ Every Sunday at midnight
"30 2 1 * *" // ๐
1st day of every month at 2:30 AM
๐ ๏ธ Setting Up Cron in TypeScript
First, letโs install the popular node-cron library:
# ๐ฆ Install dependencies
npm install node-cron
npm install --save-dev @types/node-cron
Now letโs create our first scheduled task:
// ๐ Hello, scheduled world!
import * as cron from 'node-cron';
// โฐ Simple task that runs every minute
const task = cron.schedule('* * * * *', () => {
console.log('๐ Task running every minute!');
console.log('โฐ Current time:', new Date().toLocaleString());
});
// ๐ Start the task
task.start();
๐ก Explanation: This creates a task that prints a message every minute. The asterisks mean โeveryโ for each time unit!
๐ก Practical Examples
๐ Example 1: E-commerce Cleanup System
Letโs build something useful for an online store:
// ๐ช E-commerce automation system
import * as cron from 'node-cron';
interface CartItem {
id: string;
userId: string;
productName: string;
addedAt: Date;
emoji: string; // Every product needs personality! ๐จ
}
interface User {
id: string;
email: string;
name: string;
lastActive: Date;
}
class EcommerceScheduler {
private abandonedCarts: CartItem[] = [];
private users: User[] = [];
constructor() {
this.initializeScheduledTasks();
this.seedTestData(); // ๐ฑ Add some test data
}
// ๐ Initialize all our scheduled tasks
private initializeScheduledTasks(): void {
console.log('๐ค Starting e-commerce automation...');
// ๐ง Send cart abandonment emails every hour
cron.schedule('0 * * * *', () => {
this.sendAbandonmentEmails();
});
// ๐งน Clean up old carts every day at 2 AM
cron.schedule('0 2 * * *', () => {
this.cleanupOldCarts();
});
// ๐ Generate daily sales report at 9 AM
cron.schedule('0 9 * * *', () => {
this.generateDailyReport();
});
// ๐ Update user activity every 30 minutes
cron.schedule('*/30 * * * *', () => {
this.updateUserAnalytics();
});
}
// ๐ง Send emails for abandoned carts
private sendAbandonmentEmails(): void {
console.log('๐ง Checking for abandoned carts...');
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
const abandonedItems = this.abandonedCarts.filter(cart =>
cart.addedAt < oneDayAgo
);
abandonedItems.forEach(item => {
const user = this.users.find(u => u.id === item.userId);
if (user) {
console.log(`๐ฎ Sending email to ${user.name}:`);
console.log(` "Don't forget your ${item.emoji} ${item.productName}!"`);
}
});
if (abandonedItems.length === 0) {
console.log('โ
No abandoned carts found!');
}
}
// ๐งน Clean up old cart items
private cleanupOldCarts(): void {
console.log('๐งน Starting daily cleanup...');
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const initialCount = this.abandonedCarts.length;
this.abandonedCarts = this.abandonedCarts.filter(cart =>
cart.addedAt > sevenDaysAgo
);
const removedCount = initialCount - this.abandonedCarts.length;
console.log(`๐๏ธ Cleaned up ${removedCount} old cart items`);
}
// ๐ Generate daily report
private generateDailyReport(): void {
console.log('๐ Generating daily report...');
console.log(`๐ Active users: ${this.users.length}`);
console.log(`๐ Pending carts: ${this.abandonedCarts.length}`);
console.log(`โญ Report generated at: ${new Date().toLocaleString()}`);
}
// ๐ Update analytics
private updateUserAnalytics(): void {
console.log('๐ Updating user analytics...');
const activeUsers = this.users.filter(user => {
const thirtyMinutesAgo = new Date(Date.now() - 30 * 60 * 1000);
return user.lastActive > thirtyMinutesAgo;
});
console.log(`๐ฅ ${activeUsers.length} users active in last 30 minutes`);
}
// ๐ฑ Add some test data
private seedTestData(): void {
this.users = [
{ id: '1', email: '[email protected]', name: 'Alice', lastActive: new Date() },
{ id: '2', email: '[email protected]', name: 'Bob', lastActive: new Date(Date.now() - 45 * 60 * 1000) }
];
this.abandonedCarts = [
{ id: '1', userId: '1', productName: 'TypeScript Mug', addedAt: new Date(Date.now() - 25 * 60 * 60 * 1000), emoji: 'โ' },
{ id: '2', userId: '2', productName: 'Coding Keyboard', addedAt: new Date(Date.now() - 2 * 60 * 60 * 1000), emoji: 'โจ๏ธ' }
];
}
}
// ๐ฎ Start the system
const ecommerce = new EcommerceScheduler();
๐ฏ Try it yourself: Add a weekly newsletter task that runs every Monday at 10 AM!
๐ฅ Example 2: Health Monitoring Service
Letโs create a system health monitor:
// ๐ฅ System health monitoring
interface SystemMetrics {
cpuUsage: number;
memoryUsage: number;
diskSpace: number;
timestamp: Date;
status: 'healthy' | 'warning' | 'critical';
}
class HealthMonitor {
private metrics: SystemMetrics[] = [];
private alertThresholds = {
cpu: 80, // ๐จ Alert if CPU > 80%
memory: 85, // ๐จ Alert if memory > 85%
disk: 90 // ๐จ Alert if disk > 90%
};
constructor() {
this.initializeMonitoring();
}
private initializeMonitoring(): void {
console.log('๐ฅ Starting health monitoring system...');
// ๐ Check system health every 5 minutes
cron.schedule('*/5 * * * *', () => {
this.checkSystemHealth();
});
// ๐ Generate hourly health report
cron.schedule('0 * * * *', () => {
this.generateHealthReport();
});
// ๐งน Clean old metrics weekly
cron.schedule('0 0 * * 0', () => {
this.cleanupOldMetrics();
});
}
// ๐ Check current system health
private checkSystemHealth(): void {
console.log('๐ Checking system health...');
// ๐ฒ Simulate getting real metrics
const metrics: SystemMetrics = {
cpuUsage: Math.random() * 100,
memoryUsage: Math.random() * 100,
diskSpace: Math.random() * 100,
timestamp: new Date(),
status: 'healthy'
};
// ๐จ Determine health status
if (metrics.cpuUsage > this.alertThresholds.cpu ||
metrics.memoryUsage > this.alertThresholds.memory ||
metrics.diskSpace > this.alertThresholds.disk) {
metrics.status = 'critical';
this.sendAlert(metrics);
} else if (metrics.cpuUsage > this.alertThresholds.cpu * 0.8 ||
metrics.memoryUsage > this.alertThresholds.memory * 0.8) {
metrics.status = 'warning';
}
this.metrics.push(metrics);
console.log(`๐ Health: ${this.getHealthEmoji(metrics.status)} ${metrics.status}`);
}
// ๐จ Send critical alerts
private sendAlert(metrics: SystemMetrics): void {
console.log('๐จ CRITICAL ALERT!');
console.log(`โก CPU: ${metrics.cpuUsage.toFixed(1)}%`);
console.log(`๐พ Memory: ${metrics.memoryUsage.toFixed(1)}%`);
console.log(`๐ฟ Disk: ${metrics.diskSpace.toFixed(1)}%`);
console.log('๐ Notifying admin team...');
}
// ๐ Generate health reports
private generateHealthReport(): void {
const recentMetrics = this.metrics.slice(-12); // Last hour
const avgCpu = recentMetrics.reduce((sum, m) => sum + m.cpuUsage, 0) / recentMetrics.length;
console.log('๐ Hourly Health Report:');
console.log(` โก Avg CPU: ${avgCpu.toFixed(1)}%`);
console.log(` ๐ Samples: ${recentMetrics.length}`);
console.log(` โฐ Report time: ${new Date().toLocaleString()}`);
}
private getHealthEmoji(status: string): string {
const emojis = { healthy: '๐', warning: '๐ก', critical: '๐ด' };
// TypeScript knows status is one of our defined strings!
return emojis[status as keyof typeof emojis];
}
private cleanupOldMetrics(): void {
const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const before = this.metrics.length;
this.metrics = this.metrics.filter(m => m.timestamp > oneWeekAgo);
console.log(`๐งน Cleaned ${before - this.metrics.length} old metrics`);
}
}
// ๐ฎ Start monitoring
const monitor = new HealthMonitor();
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Dynamic Cron Management
When youโre ready to level up, try dynamic scheduling:
// ๐ฏ Advanced cron job manager
interface ScheduledTask {
id: string;
name: string;
schedule: string;
task: () => void;
isActive: boolean;
lastRun?: Date;
emoji: string;
}
class DynamicCronManager {
private tasks: Map<string, cron.ScheduledTask> = new Map();
private taskConfigs: ScheduledTask[] = [];
// โ Add a new scheduled task
addTask(config: ScheduledTask): void {
try {
const scheduledTask = cron.schedule(config.schedule, () => {
console.log(`${config.emoji} Running: ${config.name}`);
config.task();
this.updateLastRun(config.id);
}, { scheduled: false });
this.tasks.set(config.id, scheduledTask);
this.taskConfigs.push(config);
if (config.isActive) {
scheduledTask.start();
console.log(`โ
Task "${config.name}" scheduled!`);
}
} catch (error) {
console.error(`โ Failed to schedule task: ${error}`);
}
}
// ๐ฎ Control task execution
toggleTask(taskId: string): void {
const task = this.tasks.get(taskId);
const config = this.taskConfigs.find(t => t.id === taskId);
if (task && config) {
if (config.isActive) {
task.stop();
config.isActive = false;
console.log(`โธ๏ธ Paused: ${config.name}`);
} else {
task.start();
config.isActive = true;
console.log(`โถ๏ธ Resumed: ${config.name}`);
}
}
}
// ๐ Get task status
getTaskStatus(): void {
console.log('๐ Task Status Report:');
this.taskConfigs.forEach(config => {
const statusEmoji = config.isActive ? 'โ
' : 'โธ๏ธ';
const lastRun = config.lastRun ? config.lastRun.toLocaleString() : 'Never';
console.log(` ${statusEmoji} ${config.emoji} ${config.name} - Last: ${lastRun}`);
});
}
private updateLastRun(taskId: string): void {
const config = this.taskConfigs.find(t => t.id === taskId);
if (config) {
config.lastRun = new Date();
}
}
}
// ๐ฎ Usage example
const cronManager = new DynamicCronManager();
// Add some dynamic tasks
cronManager.addTask({
id: 'backup',
name: 'Database Backup',
schedule: '0 2 * * *', // Daily at 2 AM
task: () => console.log('๐พ Backing up database...'),
isActive: true,
emoji: '๐พ'
});
cronManager.addTask({
id: 'analytics',
name: 'Analytics Report',
schedule: '0 9 * * 1', // Monday at 9 AM
task: () => console.log('๐ Generating weekly analytics...'),
isActive: true,
emoji: '๐'
});
๐๏ธ Advanced Topic 2: Error Handling and Retry Logic
For mission-critical tasks:
// ๐ก๏ธ Robust task execution with retries
interface TaskExecution {
attempt: number;
maxRetries: number;
retryDelay: number;
onSuccess: () => void;
onFailure: (error: Error) => void;
}
class RobustScheduler {
// ๐ Execute task with retry logic
async executeWithRetry(
taskName: string,
task: () => Promise<void>,
config: TaskExecution
): Promise<void> {
for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
try {
console.log(`๐ฏ Attempt ${attempt}/${config.maxRetries}: ${taskName}`);
await task();
console.log(`โ
Success: ${taskName}`);
config.onSuccess();
return;
} catch (error) {
console.log(`โ Attempt ${attempt} failed: ${error}`);
if (attempt === config.maxRetries) {
console.log(`๐ฅ All retries exhausted for: ${taskName}`);
config.onFailure(error as Error);
return;
}
// ๐ด Wait before retrying
console.log(`โณ Retrying in ${config.retryDelay}ms...`);
await this.delay(config.retryDelay);
}
}
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Invalid Cron Expression
// โ Wrong way - invalid cron expression!
try {
cron.schedule('60 25 * * *', () => {
console.log('This will never run! ๐ฅ');
});
} catch (error) {
console.log('๐ฐ Cron expression error!');
}
// โ
Correct way - validate expressions!
function createSafeSchedule(expression: string, task: () => void): void {
try {
if (cron.validate(expression)) {
const scheduledTask = cron.schedule(expression, task);
scheduledTask.start();
console.log('โ
Task scheduled successfully!');
} else {
console.log('โ Invalid cron expression:', expression);
}
} catch (error) {
console.error('๐ซ Scheduling failed:', error);
}
}
// ๐ฏ Usage
createSafeSchedule('0 9 * * *', () => {
console.log('๐
Good morning task!');
});
๐คฏ Pitfall 2: Memory Leaks from Unmanaged Tasks
// โ Dangerous - tasks keep running forever!
function badTaskManager(): void {
cron.schedule('* * * * *', () => {
console.log('Task running but never cleaned up! ๐ฐ');
});
} // Task keeps running even after function ends!
// โ
Safe - properly manage task lifecycle!
class SafeTaskManager {
private activeTasks: cron.ScheduledTask[] = [];
addTask(expression: string, task: () => void): void {
const scheduledTask = cron.schedule(expression, task);
this.activeTasks.push(scheduledTask);
scheduledTask.start();
}
// ๐งน Cleanup method
cleanup(): void {
console.log('๐งน Cleaning up all tasks...');
this.activeTasks.forEach(task => {
task.stop();
task.destroy();
});
this.activeTasks = [];
console.log('โ
All tasks cleaned up!');
}
}
// ๐ฏ Proper usage
const taskManager = new SafeTaskManager();
taskManager.addTask('*/5 * * * *', () => console.log('โฐ Every 5 minutes'));
// ๐งน Clean up when shutting down
process.on('SIGINT', () => {
taskManager.cleanup();
process.exit(0);
});
๐ ๏ธ Best Practices
- ๐ฏ Use Meaningful Names:
sendDailyNewsletter
nottask1
- ๐ Log Everything: Track when tasks run and if they succeed
- โก Handle Errors Gracefully: Donโt let one failed task break everything
- ๐ Consider Timezones: Use UTC for consistency across servers
- ๐ Add Retry Logic: Network requests can fail, plan for it
- ๐ Monitor Performance: Long-running tasks can impact your app
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Social Media Automation System
Create a social media scheduler that posts content automatically:
๐ Requirements:
- โ Schedule posts at optimal times (9am, 1pm, 6pm)
- ๐ท๏ธ Different content types (quote, tip, announcement)
- ๐ค Track engagement metrics
- ๐ Weekly analytics report
- ๐จ Each post needs an emoji and hashtags!
๐ Bonus Points:
- Add retry logic for failed posts
- Implement A/B testing for post times
- Create a content queue system
- Add rate limiting to avoid spam
๐ก Solution
๐ Click to see solution
// ๐ฏ Social media automation system!
interface SocialPost {
id: string;
content: string;
type: 'quote' | 'tip' | 'announcement';
emoji: string;
hashtags: string[];
scheduledTime: Date;
posted: boolean;
engagement?: {
likes: number;
shares: number;
comments: number;
};
}
class SocialMediaScheduler {
private postQueue: SocialPost[] = [];
private postedContent: SocialPost[] = [];
private optimalTimes = ['9:00', '13:00', '18:00']; // 9am, 1pm, 6pm
constructor() {
this.initializeScheduler();
this.seedContent(); // ๐ฑ Add sample content
}
// ๐ Initialize all scheduling
private initializeScheduler(): void {
console.log('๐ฑ Starting social media automation...');
// ๐ธ Post content at optimal times
this.optimalTimes.forEach(time => {
const [hour, minute] = time.split(':');
cron.schedule(`${minute} ${hour} * * *`, () => {
this.postNextContent();
});
});
// ๐ Weekly analytics report (Sunday 9am)
cron.schedule('0 9 * * 0', () => {
this.generateWeeklyReport();
});
// ๐ Update engagement metrics every 2 hours
cron.schedule('0 */2 * * *', () => {
this.updateEngagementMetrics();
});
console.log('โ
Social media scheduler active!');
}
// ๐ Add content to queue
addToQueue(post: Omit<SocialPost, 'id' | 'posted'>): void {
const newPost: SocialPost = {
...post,
id: Date.now().toString(),
posted: false
};
this.postQueue.push(newPost);
console.log(`โ Added to queue: ${post.emoji} ${post.content.substring(0, 30)}...`);
}
// ๐ธ Post next content
private postNextContent(): void {
const nextPost = this.postQueue.find(post => !post.posted);
if (!nextPost) {
console.log('๐ญ No content in queue for posting');
return;
}
this.simulatePost(nextPost);
}
// ๐ญ Simulate posting to social media
private async simulatePost(post: SocialPost): Promise<void> {
try {
console.log('๐ธ Posting to social media...');
console.log(`${post.emoji} ${post.content}`);
console.log(`๐ท๏ธ ${post.hashtags.join(' ')}`);
// ๐ฒ Simulate API call delay
await new Promise(resolve => setTimeout(resolve, 1000));
post.posted = true;
post.engagement = {
likes: Math.floor(Math.random() * 100),
shares: Math.floor(Math.random() * 20),
comments: Math.floor(Math.random() * 15)
};
this.postedContent.push(post);
console.log('โ
Post successful!');
} catch (error) {
console.log('โ Post failed, will retry later');
// ๐ Retry logic could go here
}
}
// ๐ Generate weekly analytics
private generateWeeklyReport(): void {
console.log('๐ WEEKLY SOCIAL MEDIA REPORT');
console.log('================================');
const thisWeek = this.postedContent.filter(post => {
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
return post.scheduledTime > weekAgo;
});
const totalLikes = thisWeek.reduce((sum, post) =>
sum + (post.engagement?.likes || 0), 0);
const totalShares = thisWeek.reduce((sum, post) =>
sum + (post.engagement?.shares || 0), 0);
console.log(`๐ Posts this week: ${thisWeek.length}`);
console.log(`โค๏ธ Total likes: ${totalLikes}`);
console.log(`๐ Total shares: ${totalShares}`);
console.log(`๐ฑ Avg engagement: ${(totalLikes / thisWeek.length || 0).toFixed(1)}`);
// ๐ Best performing post
const bestPost = thisWeek.reduce((best, current) => {
const currentScore = (current.engagement?.likes || 0) +
(current.engagement?.shares || 0) * 2;
const bestScore = (best.engagement?.likes || 0) +
(best.engagement?.shares || 0) * 2;
return currentScore > bestScore ? current : best;
});
if (bestPost) {
console.log(`๐ Top post: ${bestPost.emoji} ${bestPost.content.substring(0, 30)}...`);
}
}
// ๐ Update engagement metrics
private updateEngagementMetrics(): void {
console.log('๐ Updating engagement metrics...');
this.postedContent.forEach(post => {
if (post.engagement) {
// ๐ Simulate organic growth
post.engagement.likes += Math.floor(Math.random() * 5);
post.engagement.shares += Math.floor(Math.random() * 2);
post.engagement.comments += Math.floor(Math.random() * 2);
}
});
console.log('โ
Engagement metrics updated');
}
// ๐ฑ Add sample content
private seedContent(): void {
const samplePosts = [
{
content: "TypeScript makes JavaScript development so much more enjoyable! The type safety gives me confidence to refactor fearlessly.",
type: 'tip' as const,
emoji: '๐ก',
hashtags: ['#TypeScript', '#WebDev', '#Coding'],
scheduledTime: new Date()
},
{
content: "The best time to plant a tree was 20 years ago. The second best time is now. Start that coding project today!",
type: 'quote' as const,
emoji: '๐ฑ',
hashtags: ['#Motivation', '#Coding', '#Tech'],
scheduledTime: new Date()
},
{
content: "๐ Exciting news! Our new TypeScript tutorial series is live! Learn to build production-ready applications with confidence.",
type: 'announcement' as const,
emoji: '๐',
hashtags: ['#TypeScript', '#Tutorial', '#Learning'],
scheduledTime: new Date()
}
];
samplePosts.forEach(post => this.addToQueue(post));
}
// ๐ Get queue status
getQueueStatus(): void {
console.log('๐ Content Queue Status:');
console.log(` ๐ Queued: ${this.postQueue.filter(p => !p.posted).length}`);
console.log(` โ
Posted: ${this.postedContent.length}`);
}
}
// ๐ฎ Start the social media automation
const socialScheduler = new SocialMediaScheduler();
// ๐ Check status periodically
setInterval(() => {
socialScheduler.getQueueStatus();
}, 30000); // Every 30 seconds
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Create cron jobs with confidence ๐ช
- โ Avoid common scheduling mistakes that trip up beginners ๐ก๏ธ
- โ Apply best practices in real projects ๐ฏ
- โ Debug scheduling issues like a pro ๐
- โ Build automated systems with TypeScript! ๐
Remember: Cron jobs are your tireless assistants, working 24/7 to keep your applications running smoothly! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered cron job scheduling in TypeScript!
Hereโs what to do next:
- ๐ป Practice with the exercises above
- ๐๏ธ Build a personal project using scheduled tasks
- ๐ Move on to our next tutorial: Express.js with TypeScript
- ๐ Share your automated systems with other developers!
Remember: Every automation expert was once a beginner. Keep scheduling, keep learning, and most importantly, let your code work while you sleep! ๐
Happy coding! ๐๐โจ