π Live Testing
Deploy functions and test them with real URLs on your hub
π Data Transformation
See your functions transform data in real-time
π Complete Examples
Full function structures with all necessary files
π Deploy & Test
From local development to live URL in minutes
Quick Start: Your First Testable Function
Letβs build a complete function that you can test with a real URL after deployment.1
Set up your project
# Create project directory
mkdir weather-api && cd weather-api
# Initialize npm and install dependencies
npm init -y
npm install -D typescript @types/node @types/aws-lambda ts-node
# Create project structure
mkdir src lib types
2
Create your function files
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { validateCity, transformWeatherData } from './lib/weather';
import { createResponse, handleError } from './lib/utils';
export const handler = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
try {
console.log('Weather API called:', {
method: event.httpMethod,
path: event.path,
query: event.queryStringParameters
});
const city = event.queryStringParameters?.city;
// Validate input
const validation = validateCity(city);
if (!validation.isValid) {
return createResponse(400, {
error: 'Invalid city name',
details: validation.errors
});
}
// Get weather data (simulated for demo)
const rawWeatherData = await fetchWeatherData(city!);
// Transform the data
const transformedData = transformWeatherData(rawWeatherData);
return createResponse(200, {
city: city,
weather: transformedData,
timestamp: new Date().toISOString(),
source: 'Ollie Hub Weather API'
});
} catch (error) {
console.error('Weather API error:', error);
return handleError(error);
}
};
// Simulate weather API call
async function fetchWeatherData(city: string) {
// Simulate API delay
await new Promise(resolve => setTimeout(resolve, 100));
// Return mock weather data
return {
temperature: Math.floor(Math.random() * 30) + 10,
humidity: Math.floor(Math.random() * 40) + 40,
conditions: ['sunny', 'cloudy', 'rainy', 'windy'][Math.floor(Math.random() * 4)],
pressure: 1013 + Math.floor(Math.random() * 20) - 10,
windSpeed: Math.floor(Math.random() * 20),
rawTimestamp: Date.now()
};
}
export interface WeatherData {
temperature: number;
humidity: number;
conditions: string;
pressure: number;
windSpeed: number;
rawTimestamp: number;
}
export interface TransformedWeather {
temp: {
celsius: number;
fahrenheit: number;
description: string;
};
humidity: {
percentage: number;
level: string;
};
conditions: {
current: string;
emoji: string;
};
wind: {
speed: number;
description: string;
};
comfort: {
score: number;
rating: string;
};
}
export function validateCity(city: string | null): { isValid: boolean; errors: string[] } {
const errors: string[] = [];
if (!city) {
errors.push('City parameter is required');
} else if (city.trim().length < 2) {
errors.push('City name must be at least 2 characters');
} else if (!/^[a-zA-Z\s-']+$/.test(city)) {
errors.push('City name contains invalid characters');
}
return {
isValid: errors.length === 0,
errors
};
}
export function transformWeatherData(data: WeatherData): TransformedWeather {
const celsius = data.temperature;
const fahrenheit = Math.round((celsius * 9/5) + 32);
// Generate temperature description
const tempDescription = celsius < 10 ? 'Cold' :
celsius < 20 ? 'Cool' :
celsius < 25 ? 'Mild' :
celsius < 30 ? 'Warm' : 'Hot';
// Generate humidity level
const humidityLevel = data.humidity < 40 ? 'Low' :
data.humidity < 70 ? 'Moderate' : 'High';
// Generate weather emoji
const weatherEmoji = {
'sunny': 'βοΈ',
'cloudy': 'βοΈ',
'rainy': 'π§οΈ',
'windy': 'π¨'
}[data.conditions] || 'π€οΈ';
// Generate wind description
const windDescription = data.windSpeed < 5 ? 'Calm' :
data.windSpeed < 15 ? 'Light breeze' :
data.windSpeed < 25 ? 'Moderate wind' : 'Strong wind';
// Calculate comfort score (0-100)
let comfortScore = 100;
// Temperature comfort (ideal 20-25Β°C)
if (celsius < 15 || celsius > 30) comfortScore -= 30;
else if (celsius < 18 || celsius > 27) comfortScore -= 15;
// Humidity comfort (ideal 40-60%)
if (data.humidity < 30 || data.humidity > 70) comfortScore -= 20;
// Wind comfort (ideal < 15 km/h)
if (data.windSpeed > 20) comfortScore -= 15;
const comfortRating = comfortScore >= 80 ? 'Excellent' :
comfortScore >= 60 ? 'Good' :
comfortScore >= 40 ? 'Fair' : 'Poor';
return {
temp: {
celsius,
fahrenheit,
description: tempDescription
},
humidity: {
percentage: data.humidity,
level: humidityLevel
},
conditions: {
current: data.conditions,
emoji: weatherEmoji
},
wind: {
speed: data.windSpeed,
description: windDescription
},
comfort: {
score: Math.max(0, comfortScore),
rating: comfortRating
}
};
}
import { APIGatewayProxyResult } from 'aws-lambda';
export function createResponse(
statusCode: number,
data: any,
headers: Record<string, string> = {}
): APIGatewayProxyResult {
return {
statusCode,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
...headers
},
body: JSON.stringify(data, null, 2)
};
}
export function handleError(error: any): APIGatewayProxyResult {
const message = error instanceof Error ? error.message : 'Unknown error';
console.error('Function error:', {
message,
stack: error.stack,
timestamp: new Date().toISOString()
});
return createResponse(500, {
error: 'Internal server error',
message: 'Something went wrong. Please try again.',
timestamp: new Date().toISOString()
});
}
{
"name": "weather-api",
"version": "1.0.0",
"main": "index.ts",
"scripts": {
"test": "ts-node test.ts",
"build": "tsc",
"dev": "ts-node index.ts"
},
"dependencies": {
"@types/aws-lambda": "^8.10.0"
},
"devDependencies": {
"typescript": "^5.0.0",
"ts-node": "^10.9.0",
"@types/node": "^20.0.0"
}
}
3
Test locally
Create a test file to verify your function works:Run the test:
test.ts
import { handler } from './index';
import { APIGatewayProxyEvent } from 'aws-lambda';
async function testWeatherAPI() {
console.log('π§ͺ Testing Weather API Function\n');
// Test valid city
const validEvent: Partial<APIGatewayProxyEvent> = {
httpMethod: 'GET',
path: '/weather',
queryStringParameters: { city: 'London' },
headers: {},
body: null
};
console.log('Testing with city: London');
const result1 = await handler(validEvent as APIGatewayProxyEvent);
console.log('Status:', result1.statusCode);
console.log('Response:', JSON.parse(result1.body));
console.log('\n---\n');
// Test invalid city
const invalidEvent: Partial<APIGatewayProxyEvent> = {
httpMethod: 'GET',
path: '/weather',
queryStringParameters: { city: 'X' },
headers: {},
body: null
};
console.log('Testing with invalid city: X');
const result2 = await handler(invalidEvent as APIGatewayProxyEvent);
console.log('Status:', result2.statusCode);
console.log('Response:', JSON.parse(result2.body));
console.log('\n---\n');
// Test missing city
const missingEvent: Partial<APIGatewayProxyEvent> = {
httpMethod: 'GET',
path: '/weather',
queryStringParameters: null,
headers: {},
body: null
};
console.log('Testing with missing city parameter');
const result3 = await handler(missingEvent as APIGatewayProxyEvent);
console.log('Status:', result3.statusCode);
console.log('Response:', JSON.parse(result3.body));
console.log('\nβ
Local testing completed!');
}
testWeatherAPI().catch(console.error);
npm test
4
Deploy and test live
- Upload to Ollie Hub: Zip your function folder and upload it via the web interface
- Get your function URL: Copy the URL from your function dashboard
- Test with real URLs:
# Replace YOUR_FUNCTION_URL with your actual function URL
# Test valid city
curl "YOUR_FUNCTION_URL?city=Paris"
# Test another city to see different data
curl "YOUR_FUNCTION_URL?city=Tokyo"
# Test error handling
curl "YOUR_FUNCTION_URL?city=X"
# Test missing parameter
curl "YOUR_FUNCTION_URL"
{
"city": "Paris",
"weather": {
"temp": {
"celsius": 22,
"fahrenheit": 72,
"description": "Mild"
},
"humidity": {
"percentage": 65,
"level": "Moderate"
},
"conditions": {
"current": "sunny",
"emoji": "βοΈ"
},
"wind": {
"speed": 8,
"description": "Light breeze"
},
"comfort": {
"score": 85,
"rating": "Excellent"
}
},
"timestamp": "2024-01-15T10:30:00.000Z",
"source": "Ollie Hub Weather API"
}
Advanced Example: User Data Processor
Build a more complex function that processes and transforms user data:import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { validateUserData, processUser, generateUserStats } from './lib/userProcessor';
import { createResponse, handleError } from './lib/utils';
export const handler = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
try {
console.log('User processor called:', {
method: event.httpMethod,
path: event.path
});
if (event.httpMethod === 'GET') {
return createResponse(200, {
message: 'User Data Processor API',
endpoints: {
process: 'POST /process - Process user data',
stats: 'GET /stats?users=number - Generate user statistics'
},
example: {
url: 'POST YOUR_FUNCTION_URL',
body: {
name: 'John Doe',
email: 'john@example.com',
age: 28,
interests: ['technology', 'sports'],
location: 'New York'
}
}
});
}
if (event.httpMethod === 'POST') {
const userData = JSON.parse(event.body || '{}');
// Validate user data
const validation = validateUserData(userData);
if (!validation.isValid) {
return createResponse(400, {
error: 'Invalid user data',
details: validation.errors
});
}
// Process and transform user data
const processedUser = processUser(userData);
return createResponse(200, {
original: userData,
processed: processedUser,
transformation: {
added: ['id', 'slug', 'category', 'score', 'recommendations'],
computed: ['ageGroup', 'riskLevel', 'primaryInterest'],
formatted: ['email', 'name', 'location']
}
});
}
// Handle stats endpoint
if (event.path?.includes('stats')) {
const userCount = parseInt(event.queryStringParameters?.users || '10');
const stats = generateUserStats(userCount);
return createResponse(200, {
stats,
generated: userCount,
timestamp: new Date().toISOString()
});
}
return createResponse(405, { error: 'Method not allowed' });
} catch (error) {
return handleError(error);
}
};
export interface UserData {
name: string;
email: string;
age: number;
interests: string[];
location: string;
}
export interface ProcessedUser extends UserData {
id: string;
slug: string;
emailDomain: string;
ageGroup: string;
category: string;
score: number;
riskLevel: string;
primaryInterest: string;
recommendations: string[];
metadata: {
processed: string;
version: string;
};
}
export function validateUserData(data: any): { isValid: boolean; errors: string[] } {
const errors: string[] = [];
if (!data.name || data.name.trim().length < 2) {
errors.push('Name must be at least 2 characters');
}
if (!data.email || !data.email.includes('@')) {
errors.push('Valid email is required');
}
if (!data.age || data.age < 13 || data.age > 120) {
errors.push('Age must be between 13 and 120');
}
if (!Array.isArray(data.interests) || data.interests.length === 0) {
errors.push('At least one interest is required');
}
if (!data.location || data.location.trim().length < 2) {
errors.push('Location is required');
}
return {
isValid: errors.length === 0,
errors
};
}
export function processUser(userData: UserData): ProcessedUser {
// Generate unique ID
const id = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// Create URL-friendly slug
const slug = userData.name.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
// Extract email domain
const emailDomain = userData.email.split('@')[1];
// Determine age group
const ageGroup = userData.age < 18 ? 'minor' :
userData.age < 25 ? 'young-adult' :
userData.age < 35 ? 'adult' :
userData.age < 50 ? 'middle-age' :
userData.age < 65 ? 'mature' : 'senior';
// Categorize user based on interests
const techInterests = ['technology', 'programming', 'gaming', 'ai', 'software'];
const sportsInterests = ['sports', 'fitness', 'running', 'gym', 'soccer'];
const artsInterests = ['art', 'music', 'painting', 'writing', 'photography'];
let category = 'general';
if (userData.interests.some(i => techInterests.includes(i.toLowerCase()))) {
category = 'tech-enthusiast';
} else if (userData.interests.some(i => sportsInterests.includes(i.toLowerCase()))) {
category = 'fitness-focused';
} else if (userData.interests.some(i => artsInterests.includes(i.toLowerCase()))) {
category = 'creative-minded';
}
// Calculate engagement score
let score = 50; // Base score
score += userData.interests.length * 10; // More interests = higher engagement
score += userData.age < 30 ? 15 : userData.age < 50 ? 10 : 5; // Age factor
score += emailDomain === 'gmail.com' ? 5 :
emailDomain.includes('.edu') ? 15 :
emailDomain.includes('.gov') ? 10 : 0; // Email domain factor
// Determine risk level
const riskLevel = score > 80 ? 'low' :
score > 60 ? 'medium' :
score > 40 ? 'high' : 'very-high';
// Get primary interest
const primaryInterest = userData.interests[0] || 'general';
// Generate recommendations
const recommendations = generateRecommendations(category, ageGroup, userData.interests);
return {
...userData,
id,
slug,
emailDomain,
ageGroup,
category,
score: Math.min(100, score),
riskLevel,
primaryInterest,
recommendations,
metadata: {
processed: new Date().toISOString(),
version: '1.0.0'
}
};
}
function generateRecommendations(category: string, ageGroup: string, interests: string[]): string[] {
const recommendations: string[] = [];
// Category-based recommendations
if (category === 'tech-enthusiast') {
recommendations.push('Try our new AI-powered productivity tools');
recommendations.push('Join our developer community forum');
} else if (category === 'fitness-focused') {
recommendations.push('Check out our personalized workout plans');
recommendations.push('Connect with local fitness groups');
} else if (category === 'creative-minded') {
recommendations.push('Explore our digital art creation tools');
recommendations.push('Join our creative challenges');
}
// Age-based recommendations
if (ageGroup === 'young-adult') {
recommendations.push('Student discount programs available');
} else if (ageGroup === 'mature' || ageGroup === 'senior') {
recommendations.push('Premium support and consultation services');
}
// Interest-based recommendations
if (interests.includes('travel')) {
recommendations.push('Virtual travel experiences and planning tools');
}
if (interests.includes('cooking')) {
recommendations.push('Recipe recommendation engine');
}
return recommendations.slice(0, 3); // Limit to 3 recommendations
}
export function generateUserStats(userCount: number) {
const ageGroups = ['young-adult', 'adult', 'middle-age', 'mature'];
const categories = ['tech-enthusiast', 'fitness-focused', 'creative-minded', 'general'];
const locations = ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix', 'Philadelphia'];
const stats = {
totalUsers: userCount,
demographics: {
ageGroups: {} as Record<string, number>,
categories: {} as Record<string, number>,
locations: {} as Record<string, number>
},
averageScore: 0,
riskDistribution: {
low: 0,
medium: 0,
high: 0,
'very-high': 0
},
topInterests: [] as Array<{ interest: string; count: number }>
};
let totalScore = 0;
const interestCounts: Record<string, number> = {};
// Generate random but realistic distributions
for (let i = 0; i < userCount; i++) {
// Age group distribution
const ageGroup = ageGroups[Math.floor(Math.random() * ageGroups.length)];
stats.demographics.ageGroups[ageGroup] = (stats.demographics.ageGroups[ageGroup] || 0) + 1;
// Category distribution
const category = categories[Math.floor(Math.random() * categories.length)];
stats.demographics.categories[category] = (stats.demographics.categories[category] || 0) + 1;
// Location distribution
const location = locations[Math.floor(Math.random() * locations.length)];
stats.demographics.locations[location] = (stats.demographics.locations[location] || 0) + 1;
// Score calculation
const score = Math.floor(Math.random() * 50) + 40; // 40-90 range
totalScore += score;
// Risk distribution
const risk = score > 80 ? 'low' : score > 60 ? 'medium' : score > 40 ? 'high' : 'very-high';
stats.riskDistribution[risk as keyof typeof stats.riskDistribution]++;
// Interest tracking
const possibleInterests = ['technology', 'sports', 'art', 'travel', 'cooking', 'music', 'reading'];
const userInterests = possibleInterests.slice(0, Math.floor(Math.random() * 3) + 1);
userInterests.forEach(interest => {
interestCounts[interest] = (interestCounts[interest] || 0) + 1;
});
}
stats.averageScore = Math.round(totalScore / userCount);
stats.topInterests = Object.entries(interestCounts)
.sort(([,a], [,b]) => b - a)
.slice(0, 5)
.map(([interest, count]) => ({ interest, count }));
return stats;
}
import { APIGatewayProxyResult } from 'aws-lambda';
export function createResponse(
statusCode: number,
data: any,
headers: Record<string, string> = {}
): APIGatewayProxyResult {
return {
statusCode,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
...headers
},
body: JSON.stringify(data, null, 2)
};
}
export function handleError(error: any): APIGatewayProxyResult {
const message = error instanceof Error ? error.message : 'Unknown error';
console.error('Function error:', {
message,
stack: error.stack,
timestamp: new Date().toISOString()
});
return createResponse(500, {
error: 'Internal server error',
message: 'Something went wrong. Please try again.',
timestamp: new Date().toISOString(),
requestId: Math.random().toString(36).substr(2, 9)
});
}
Test the User Processor
- Local Testing
- Live URL Testing
- Browser Testing
test-user-processor.ts
import { handler } from './index';
import { APIGatewayProxyEvent } from 'aws-lambda';
async function testUserProcessor() {
console.log('π§ͺ Testing User Data Processor\n');
// Test API info
const infoEvent: Partial<APIGatewayProxyEvent> = {
httpMethod: 'GET',
path: '/',
queryStringParameters: null,
headers: {},
body: null
};
console.log('1. Getting API information:');
const infoResult = await handler(infoEvent as APIGatewayProxyEvent);
console.log('Response:', JSON.parse(infoResult.body));
console.log('\n---\n');
// Test user processing
const userData = {
name: 'Alice Johnson',
email: 'alice@example.com',
age: 28,
interests: ['technology', 'travel', 'photography'],
location: 'San Francisco'
};
const processEvent: Partial<APIGatewayProxyEvent> = {
httpMethod: 'POST',
path: '/process',
body: JSON.stringify(userData),
headers: { 'Content-Type': 'application/json' },
queryStringParameters: null
};
console.log('2. Processing user data:');
console.log('Input:', userData);
const processResult = await handler(processEvent as APIGatewayProxyEvent);
console.log('Output:', JSON.parse(processResult.body));
console.log('\n---\n');
// Test stats generation
const statsEvent: Partial<APIGatewayProxyEvent> = {
httpMethod: 'GET',
path: '/stats',
queryStringParameters: { users: '100' },
headers: {},
body: null
};
console.log('3. Generating user statistics:');
const statsResult = await handler(statsEvent as APIGatewayProxyEvent);
console.log('Stats:', JSON.parse(statsResult.body));
console.log('\nβ
All tests completed!');
}
testUserProcessor().catch(console.error);
After deploying, test with these commands:
# Get API information
curl YOUR_FUNCTION_URL
# Process user data
curl -X POST YOUR_FUNCTION_URL \
-H "Content-Type: application/json" \
-d '{
"name": "John Smith",
"email": "john@gmail.com",
"age": 32,
"interests": ["sports", "fitness", "technology"],
"location": "Chicago"
}'
# Generate statistics for 50 users
curl "YOUR_FUNCTION_URL?users=50"
# Test error handling
curl -X POST YOUR_FUNCTION_URL \
-H "Content-Type: application/json" \
-d '{
"name": "X",
"email": "invalid-email",
"age": 5
}'
You can also test in your browser by visiting:For POST requests, use a tool like Postman or create a simple HTML form:
YOUR_FUNCTION_URL
YOUR_FUNCTION_URL?users=25
<!DOCTYPE html>
<html>
<head>
<title>Test User Processor</title>
</head>
<body>
<h2>Test User Data Processor</h2>
<form id="userForm">
<input type="text" name="name" placeholder="Name" required><br><br>
<input type="email" name="email" placeholder="Email" required><br><br>
<input type="number" name="age" placeholder="Age" required><br><br>
<input type="text" name="interests" placeholder="Interests (comma-separated)" required><br><br>
<input type="text" name="location" placeholder="Location" required><br><br>
<button type="submit">Process User</button>
</form>
<div id="result"></div>
<script>
document.getElementById('userForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const userData = {
name: formData.get('name'),
email: formData.get('email'),
age: parseInt(formData.get('age')),
interests: formData.get('interests').split(',').map(s => s.trim()),
location: formData.get('location')
};
try {
const response = await fetch('YOUR_FUNCTION_URL', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
const result = await response.json();
document.getElementById('result').innerHTML =
'<pre>' + JSON.stringify(result, null, 2) + '</pre>';
} catch (error) {
document.getElementById('result').innerHTML = 'Error: ' + error.message;
}
});
</script>
</body>
</html>
Real-World Data Transformation Examples
Deployment Checklist
1
Local testing passes
β
All functions run without errors locally
β Data transformation works as expected
β Error handling catches edge cases
β TypeScript compiles without warnings
β Data transformation works as expected
β Error handling catches edge cases
β TypeScript compiles without warnings
2
Function structure is correct
β
β
β Helper files are properly structured
β No sensitive data in source code
index.ts exists with exported handlerβ
package.json includes all dependenciesβ Helper files are properly structured
β No sensitive data in source code
3
Upload and configure
β
Function uploaded to Ollie Hub
β Environment variables configured
β Function type set correctly (Request/Task/Response)
β Memory and timeout settings appropriate
β Environment variables configured
β Function type set correctly (Request/Task/Response)
β Memory and timeout settings appropriate
4
Live testing
β
Function URL accessible
β Data transformation works on live data
β Error responses are user-friendly
β Performance is acceptable
β Data transformation works on live data
β Error responses are user-friendly
β Performance is acceptable
Next Steps
Deploy Your Function
Learn how to deploy your functions to Ollie Hub
Environment Variables
Securely manage configuration and API keys
Function Types
Understand Request, Response, and Task functions
Examples Gallery
Explore more real-world function examples
Ready to Build? Start with the Weather API example above - itβs designed to work immediately and show you real data transformation. Youβll have a working function with a testable URL in under 10 minutes!