The modern backend can feel like a choice between two extremes: a bulky, all-or-nothing monolith or a sprawling, complex web of microservices. But what if there was a third way? What if you could break down your logic into its smallest, most essential parts and deploy each one as a standalone, scalable API?
This is the power of atomic, composable functions. At function.do, we're built on a simple premise: Deploy Functions. Get APIs. You write a single-purpose piece of code, and we instantly give you a secure, production-ready API endpoint for it. No boilerplate, no server management, just your logic.
To show you how transformative this approach is, let's explore five practical use cases where deploying a function as an API isn't just easier—it's smarter.
The Challenge: Every website needs a contact form. The traditional approach involves setting up a backend server (like Express or Flask), creating a route, writing validation logic, parsing the request, and integrating with an email service like SendGrid or Mailgun. That's a lot of overhead for one simple form.
The Function-as-API Solution: Create a single function that handles the entire process. This function becomes your form's action endpoint. It's isolated, secure, and scales automatically, so you never have to worry about a traffic spike crashing your "contact me" page.
import { Fn } from '@do-are/sdk';
import { sendEmail } from './lib/email-service'; // Your email helper
/**
* @description Receives contact form data and sends a notification email.
* @param { email: string, name: string, message: string }
* @returns { success: boolean }
*/
export const handleContactForm: Fn = async ({ email, name, message }) => {
if (!email || !name || !message) {
throw new Error('All fields are required.');
}
// Your logic to send the email
await sendEmail({
to: 'your-email@example.com',
subject: `New message from ${name}`,
text: message,
});
return { success: true };
};
// Deploys to: POST https://your-name.function.do/handleContactForm
The Challenge: Services like Stripe use webhooks to notify your application about events, such as a successful payment or a failed subscription. You need to build a highly reliable and secure endpoint to receive these webhooks, verify their cryptographic signature (to ensure they're actually from Stripe), and then perform an action, like updating a user's subscription status in your database.
The Function-as-API Solution: A dedicated function is the perfect tool for a webhook. It provides an immediate, public URL for Stripe to call. The function's single responsibility is to validate the webhook and process the event. Because it's an isolated unit, its success or failure doesn't impact the rest of your application.
import { Fn } from '@do-are/sdk';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
/**
* @description Verifies and processes incoming Stripe webhooks.
* @param { body: any, headers: any } - Raw request body and headers.
* @returns { received: boolean }
*/
export const processStripeWebhook: Fn = async ({ body, headers }) => {
const sig = headers['stripe-signature'];
let event;
try {
// The raw body is needed for signature verification
event = stripe.webhooks.constructEvent(body, sig, endpointSecret);
} catch (err) {
throw new Error(`Webhook Error: ${err.message}`);
}
// Handle the event (e.g., payment_intent.succeeded)
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
console.log('Payment was successful!', paymentIntent.id);
// TODO: Fulfill the purchase, grant access, etc.
break;
// ... handle other event types
default:
console.log(`Unhandled event type ${event.type}`);
}
return { received: true };
};
The Challenge: You want to create a custom Slack command, like /deploy production, that triggers a CI/CD pipeline. Slack needs a public "Request URL" to send the command's payload to. Building and deploying a dedicated service just for this small piece of interactivity is overkill.
The Function-as-API Solution: A function.do endpoint is the ideal Request URL. Your function receives the payload from Slack, parses the command, and can then trigger other services, call another API, or simply post a message back to the channel.
import { Fn } from '@do-are/sdk';
import { callDeploymentApi } from './lib/ci-cd-service';
/**
* @description Handles the /deploy slash command from Slack.
* @param { text: string, user_name: string } - Payload from Slack.
* @returns { text: string } - Formatted message to send back to Slack.
*/
export const handleDeployCommand: Fn = async ({ text, user_name }) => {
const targetEnvironment = text.trim(); // "production"
if (!targetEnvironment) {
return { text: "Please specify an environment, e.g., `/deploy production`" };
}
// Asynchronously trigger the deployment
callDeploymentApi({ environment: targetEnvironment, user: user_name });
// Immediately respond to the user in Slack
return {
response_type: 'in_channel',
text: `🚀 Okay, ${user_name}! Kicking off deployment to *${targetEnvironment}*.`,
};
};
The Challenge: Your application allows users to upload avatars, but you need to display them as 50x50 thumbnails. Processing images is CPU-intensive. Running this on your main application server can slow down other requests and lead to performance bottlenecks.
The Function-as-API Solution: Offload image processing to a dedicated function. Your main application can upload the original image to cloud storage (like S3) and then call your resizeImage function. This function downloads the image, resizes it using a library like sharp (yes, you can use NPM packages!), and saves the thumbnail back to storage. The task is handled in an isolated, scalable environment.
import { Fn } from '@do-are/sdk';
import sharp from 'sharp';
import { downloadFromS3, uploadToS3 } from './lib/s3-helper';
/**
* @description Creates a thumbnail for a given image.
* @param { sourceKey: string } - The S3 key of the original image.
* @returns { thumbnailKey: string }
*/
export const createThumbnail: Fn = async ({ sourceKey }) => {
const imageBuffer = await downloadFromS3(sourceKey);
const thumbnailBuffer = await sharp(imageBuffer)
.resize(50, 50)
.toBuffer();
const thumbnailKey = `thumbnails/${sourceKey}`;
await uploadToS3(thumbnailBuffer, thumbnailKey);
return { thumbnailKey };
};
The Challenge: Multiple parts of your system need to validate a US zip code or enrich a user profile with data from a third-party API like Clearbit. Duplicating this logic is inefficient and error-prone. Building a full microservice for it can be too complex.
The Function-as-API Solution: Create a small, "atomic" function that does one thing perfectly. A validateAddress function can take an address object, call an external geocoding API, and return a standardized, validated address. Any other service—or even another function.do function—can then call this simple API endpoint, ensuring your logic is centralized, reusable, and easy to maintain.
import { Fn } from '@do-are/sdk';
/**
* @description Validates a US zip code format.
* @param { zip: string }
* @returns { isValid: boolean, formattedZip: string }
*/
export const validateZipCode: Fn = async ({ zip }) => {
const isValid = /^\d{5}(-\d{4})?$/.test(zip);
if (!isValid) {
throw new Error("Invalid zip code format.");
}
return { isValid: true, formattedZip: zip };
};
// Now any part of your system can validate a zip code
// with a simple API call.
Individually, these functions are useful. But the real power emerges when you compose them.
You build complex, robust workflows by chaining together simple, independent, and reusable blocks of logic. This is the core of a composable architecture—and with function.do, it's the most natural way to build.
If you're tired of boilerplate and managing infrastructure, function.do offers radical simplicity. While platforms like AWS Lambda give you the building blocks, we give you a finished solution. You write the logic; we give you the API.
Get started for free and deploy your first function in minutes.
Visit function.do to learn more.
Q: What is an atomic function?
A: An atomic function is a small, self-contained unit of code designed to perform one specific task perfectly. On the function.do platform, each function you export becomes its own dedicated, scalable API endpoint.
Q: How is this different from AWS Lambda or Google Cloud Functions?
A: function.do offers radical simplicity. While other platforms provide building blocks, we provide a complete, production-ready solution. You write the logic, and we instantly generate a secure, scalable, and documented API. There is zero boilerplate or infrastructure to manage.
Q: Can my function use external NPM packages?
A: Yes. Simply define your dependencies in a package.json file alongside your function code. The platform automatically installs them during the deployment process.
Q: Are these functions composable?
A: Absolutely. You can compose functions by calling one function.do endpoint from another using our SDK or a standard HTTP client. This allows you to build complex workflows from simple, reusable, and independently scalable parts.