Practice and reinforce the concepts from Lesson 12
In this activity, you'll:
Total time: 60-75 minutes
What you'll do: Access the deployment template Time: 5 minutes
Access the template here: Deployment Configuration Template
git clone
and navigate to the template folderfrontend/
- Complete Quest Tracker dashboardbackend/
- Quest API serverdeploy-scripts/
- Deployment automationdocs/
- Deployment guides⚠️ Important: Before You Start Make sure you have accounts on:
- GitHub (for code repository)
- Netlify (for frontend deployment)
- Railway (for backend deployment)
What you'll do: Optimize and prepare your applications Time: 15 minutes
Create frontend/.env.production
:
# Production API Configuration
REACT_APP_API_URL=https://your-api.railway.app
REACT_APP_API_KEY=your_production_api_key
REACT_APP_ENVIRONMENT=production
# Analytics and Monitoring
REACT_APP_GOOGLE_ANALYTICS=GA-XXXXXXXXX
REACT_APP_SENTRY_DSN=your_sentry_dsn
# Feature Flags
REACT_APP_ENABLE_PWA=true
REACT_APP_ENABLE_PUSH_NOTIFICATIONS=true
Create backend/.env.production
:
# Server Configuration
NODE_ENV=production
PORT=3000
# Database
DATABASE_URL=your_database_url
# Security
JWT_SECRET=your_jwt_secret_key
API_RATE_LIMIT=100
# External APIs
QUEST_API_URL=https://your-quest-api.railway.app
QUEST_API_KEY=your_quest_api_key
# Monitoring
SENTRY_DSN=your_backend_sentry_dsn
LOG_LEVEL=info
Update frontend/package.json
with build scripts:
{
"scripts": {
"build": "react-scripts build",
"build:prod": "npm run build && npm run optimize",
"optimize": "npm run compress && npm run analyze",
"compress": "gzip -k build/static/js/*.js build/static/css/*.css",
"analyze": "npx webpack-bundle-analyzer build/static/js/*.js --no-open",
"test:prod": "npm run build && serve -s build -p 3000"
}
}
Update backend/server.js
:
// Health check endpoints for monitoring
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
memory: process.memoryUsage(),
version: process.env.npm_package_version || '1.0.0'
});
});
app.get('/api/status', (req, res) => {
res.json({
api_status: 'operational',
endpoints: {
quests: '/api/quests',
quest_by_id: '/api/quests/:id',
create_quest: 'POST /api/quests',
update_quest: 'PUT /api/quests/:id',
delete_quest: 'DELETE /api/quests/:id'
},
rate_limits: {
requests_per_minute: 100,
current_usage: 'Available via /api/stats'
},
last_updated: new Date().toISOString()
});
});
// Simple stats endpoint
let requestCount = 0;
app.use('/api/', (req, res, next) => {
requestCount++;
next();
});
app.get('/api/stats', (req, res) => {
res.json({
total_requests: requestCount,
requests_per_minute: Math.round(requestCount / (process.uptime() / 60)),
active_connections: req.socket.server._connections || 0,
server_uptime: process.uptime()
});
});
What you'll do: Deploy your Quest Tracker dashboard Time: 20 minutes
Create frontend/netlify.toml
:
[build]
publish = "build"
command = "npm run build:prod"
[build.environment]
NODE_VERSION = "18"
NPM_VERSION = "9"
[[redirects]]
from = "/api/*"
to = "https://your-api.railway.app/api/:splat"
status = 200
force = true
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[[headers]]
for = "/static/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/*.js"
[headers.values]
Content-Type = "application/javascript"
[[headers]]
for = "/*.css"
[headers.values]
Content-Type = "text/css"
[context.production]
[context.production.environment]
REACT_APP_ENVIRONMENT = "production"
[context.deploy-preview]
[context.production.environment]
REACT_APP_ENVIRONMENT = "staging"
Follow these steps:
# In your StackBlitz terminal (if available) or locally
git init
git add .
git commit -m "Initial commit: Quest Tracker app ready for deployment"
git remote add origin https://github.com/yourusername/quest-tracker.git
git push -u origin main
npm run build
build
frontend
# In Netlify dashboard > Site settings > Environment variables
REACT_APP_API_URL=https://your-api.railway.app
REACT_APP_API_KEY=demo_key_12345
REACT_APP_ENVIRONMENT=production
Your app will be available at: https://your-app-name.netlify.app
Test these features:
What you'll do: Deploy your Quest API Time: 15 minutes
Create backend/railway.toml
:
[build]
builder = "NIXPACKS"
buildCommand = "npm install"
[deploy]
startCommand = "npm start"
healthcheckPath = "/health"
healthcheckTimeout = 300
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 10
[env]
NODE_ENV = "production"
PORT = "$PORT"
Update backend/package.json
:
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"build": "echo 'No build step required'",
"test": "jest",
"health": "curl http://localhost:$PORT/health"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=9.0.0"
}
}
Follow these steps:
backend/
folderbackend
folder# In Railway dashboard > Variables
NODE_ENV=production
PORT=3000
API_RATE_LIMIT=1000
https://your-app.railway.app
)What you'll do: Configure professional domains Time: 10 minutes
In Netlify dashboard:
quest-tracker.yourdomain.com
)# Add CNAME record in your DNS provider
Type: CNAME
Name: quest-tracker
Value: your-app-name.netlify.app
In Railway dashboard:
quest-api.yourdomain.com
)// Update your frontend API configuration
const API_BASE_URL = process.env.REACT_APP_API_URL || 'https://quest-api.yourdomain.com';
What you'll do: Monitor your live applications Time: 10 minutes
Create frontend/src/utils/monitoring.js
:
// Simple application monitoring for your Quest Tracker app
class AppMonitor {
constructor() {
this.errors = [];
this.apiCalls = [];
this.userActions = [];
}
logError(error, context = {}) {
const errorLog = {
timestamp: new Date().toISOString(),
message: error.message,
stack: error.stack,
context: context,
url: window.location.href,
userAgent: navigator.userAgent
};
this.errors.push(errorLog);
console.error('App Error:', errorLog);
// In production, send to monitoring service
if (process.env.REACT_APP_ENVIRONMENT === 'production') {
this.sendToMonitoring('error', errorLog);
}
}
logApiCall(endpoint, duration, status) {
const apiLog = {
timestamp: new Date().toISOString(),
endpoint: endpoint,
duration: duration,
status: status
};
this.apiCalls.push(apiLog);
// Track slow API calls
if (duration > 3000) {
console.warn('Slow API call:', apiLog);
}
}
logUserAction(action, data = {}) {
const actionLog = {
timestamp: new Date().toISOString(),
action: action,
data: data,
page: window.location.pathname
};
this.userActions.push(actionLog);
}
sendToMonitoring(type, data) {
// Mock sending to monitoring service
fetch('/api/monitoring', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type, data })
}).catch(err => console.log('Monitoring service unavailable'));
}
getStats() {
return {
errors: this.errors.length,
apiCalls: this.apiCalls.length,
averageApiTime: this.apiCalls.length > 0
? this.apiCalls.reduce((sum, call) => sum + call.duration, 0) / this.apiCalls.length
: 0,
userActions: this.userActions.length
};
}
}
export const appMonitor = new AppMonitor();
// Global error handler
window.addEventListener('error', (event) => {
appMonitor.logError(event.error, { type: 'unhandled_error' });
});
window.addEventListener('unhandledrejection', (event) => {
appMonitor.logError(new Error(event.reason), { type: 'unhandled_promise_rejection' });
});
Create frontend/src/utils/performance.js
:
// Performance monitoring utilities
export function measureApiCall(apiFunction, endpoint) {
return async (...args) => {
const startTime = performance.now();
try {
const result = await apiFunction(...args);
const duration = performance.now() - startTime;
appMonitor.logApiCall(endpoint, duration, 'success');
return result;
} catch (error) {
const duration = performance.now() - startTime;
appMonitor.logApiCall(endpoint, duration, 'error');
appMonitor.logError(error, { endpoint });
throw error;
}
};
}
// Page load performance
export function trackPageLoad() {
window.addEventListener('load', () => {
const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart;
console.log(`Page loaded in ${loadTime}ms`);
appMonitor.logUserAction('page_load', {
loadTime: loadTime,
page: window.location.pathname
});
});
}
// Track user interactions
export function trackUserInteraction(element, action) {
element.addEventListener('click', () => {
appMonitor.logUserAction(action, {
element: element.tagName,
id: element.id,
class: element.className
});
});
}
Try these extra challenges:
Set up automated testing and deployment with GitHub Actions
Configure CloudFlare or AWS CloudFront for global content delivery
Deploy a production database and migrate your API to use it
Congratulations! You've built and deployed a complete application with:
Your app is now live on the internet! Share it with friends and watch them create quests, track their progress, and achieve their goals!
Consider extending your app with:
Deployment failing?
API not connecting?
Performance issues?