tl;dr (short version)
Serverless is a cloud technology that allows you to focus more on your software application logic, rather than on the deployment or maintenance of servers hosting your application. This leaves more time to improve your application, test it thoroughly, and add new functions.
The alternative to serverless is server-oriented deployment, which has its pros and cons. In general, if you have an application that needs to be accessed by the public via the internet, serverless is a good option, especially if you are in the beginning phase of application development.
Deep Dive
Take the following Typescript/Javascript code as an example:
import {
fetchCustomerActiveProject,
fetchCustomerActiveProject,
fetchInvoice,
generateInvoicePDF,
} from "../api"
export function generateCustomerInvoice(customerId: string) {
const activeProject = fetchCustomerActiveProject(customerId)
const invoice = fetchInvoice(activeProject.id)
const pdfGenResult = generateInvoicePDF(invoice)
if (!pdfGenResult.success) {
throw new Error(pdfGenResult.error)
}
return pdfGenResult.filepath
}
Nothing fancy here, the function accepts a customer’s ID and then generates a PDF of the invoice. We are not interested in the low-level details; assume the function works correctly.
So we want to call this function from our web, mobile, or desktop application — i.e., by clicking on a download PDF button and triggering this call. Take this UI as an example — user click on the download file icon next to the invoice and the function would be called (or triggered).
How can we achieve this?
The usual way or the “non-serverless way” to do this is by running something like an Express Node.js app (maybe proxied via an Nginx server), that has a route like so:
/api/generateInvoice?customerId=75612312
And the code would be something like this:
const express = require("express")
const app = express()
const port = 3000
const { generateCustomerInvoice } = require("api")
// Middleware to parse JSON bodies
app.use(express.json())
// Route to generate invoice
app.get("/api/generateInvoice", (req, res) => {
const customerId = req.query.customerId
if (!customerId) {
return res.status(400).json({ error: "Customer ID is required" })
}
const invoice = generateCustomerInvoice(customerId)
res.json(invoice)
})
// Start the server
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`)
})
And to run this in a production environment, you would have to manually provision a computer or a virtual machine (it can be Docker as well) and host this there.
So the steps involved in deploying this would be the following:
- Create a virtual machine (specify RAM, Storage, CPU Cores and Speed)
- Install Node.js
- Install Express
- transfer the code inside that machine
- run it
- monitor it
in theory sounds easy enough, but in real world —not so much.
What are the issue with this solution?
Here it comes — once you do all that (after deploying your code), you have the following responsibilities:
Manage updating the operating system to make sure no vulnerabilities exist. This meaning keeping up to date with issues, patching your kernel etc
Make sure you set up the firewall correctly so it’s secure and no one can access the machine. For this case, the firewall configuration is going to be easy but for larger and more complex application — managing IPTables requires a lot of reading
In case of traffic spikes, increase the machine resources or create a secondary one and use a load balancer. But after the spikes, you have to undo that and go back to a single server
Leave the machine running at all times, even if the invoice creation function would only be called once in a while — so you’re wasting CPU resources, electricity, and money.
In case something goes wrong — e.g., PC runs out of memory and crashes — you have to monitor the machine, restart it, and recover it.
All of this for just this simple function — imagine adding a database to this system or a caching system etc, now you have to monitor more stuff. This is precious time you have to spend supporting your function — where instead you could be updating your app or improve it.
This is where serverless comes in.
Serverless
Serverless solves all of the mentioned problems by giving you the tools to simply upload your function to the serverless provider of your choice, specify the API route that would call this function, and that’s it.
Technically speaking, “Serverless” is a misnomer, because you are very much still using a server, BUT you are not manually provisioning and managing the server. If you really want to name it properly, it should’ve been called something like “3rd Party Managed Servers” instead, but I think “Serverless” is a more marketable term.
Serverless will manage updating any underlying OS it’s running on, firewall, scale up when traffic spikes and scale down when there is no traffic, and handle all server related issues.
...
it’s a perfect solution for startups and proof of concept projects, as it reduces the time needed to fix and manage the supporting servers — so you have more time to work on application logic
...
So to define Serverless formally, it is a cloud-native computing model, (meaning it is feature that is only available on 3rd party cloud services like AWS, Cloudflare, fly.io, etc), which allows you to build and run applications and services without having to manage infrastructure.
Why not use Serverless?
As mentioned in the last sentence, serverless is a cloud-native solution, so if you plan on hosting your application on a private intranet — let’s say, a company that has local servers like a military-based company, or somewhere with slow or no internet connection — then serverless is not the right solution for you.
Second, the pricing model is not deterministic — if you are not careful with your lambda configuration, and let’s say a DDoS attack happens, it will incur a large amount of charges. Or if you get unexpected high traffic without any monetization strategy, the same issue will occur.
Thirdly, my experience with serverless has been slow — the development process is very slow since you have to push your changes to the provider to test out the functions, whereas if you develop locally, like in a Docker environment, it would be much faster. In order to speed up your dev process, you actually have to become a better defensive coder to move faster developing code.
Sample Psudo Code
This example demonstrates using AWS, AWS CDK, and TypeScript to create a sample codebase that automatically:
- Provisions serverless resources on AWS
- Uploads the Lambda function code
- Creates an API route to invoke the function
This is the main entry point of the code
const config = getConfig()
// cdk starting point
new SampleAppStack(app, "RedRobot", {
env: {
account: config.awsAccountId,
region: config.awsRegion,
},
})
this is the SampleAppStack call which create the lambda and REST resources (API Gateway)
import * as cdk from "aws-cdk-lib"
import { Construct } from "constructs"
import * as lambdaNodeJs from "aws-cdk-lib/aws-lambda-nodejs"
import * as lambda from "aws-cdk-lib/aws-lambda"
import * as apigateway from "aws-cdk-lib/aws-apigateway"
export class SampleAppStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)
// create lambda
const lambdaPdfGen = new lambdaNodeJs.NodejsFunction(
scope,
"pdfGenerator",
{
entry: "../../lambda/pdfGenerator",
handler: "index",
runtime: lambda.Runtime.NODEJS_20_X,
}
)
// top level api gateway construct
const restApi = new apigateway.RestApi(this, "restApi", {})
const apiResource = restApi.root.addResource("/api/generatePDF")
// attach the lamba PDF generator call to the rest api get call
apiResource.addMethod("GET", new apigateway.LambdaIntegration(lambdaPdfGen))
}
}
and this is the lambda function
// lambda/pdfGenerator
import * as lambda from "aws-lambda";
import {
fetchCustomerActiveProject,
fetchCustomerActiveProject,
fetchInvoice,
generateInvoicePDF,
parseCustomerId
} from '../api'
export const generateCustomerInvoiceLambda: lambda.APIGatewayProxyHandler =
async function (event: lambda.APIGatewayProxyEvent, context: lambda.Context) {
try {
const { customerId } = parseCustomerId(event.body);
const activeProject = fetchCustomerActiveProject(customerId);
const invoice = fetchInvoice(activeProject.id);
const pdfGenResult = generateInvoicePDF(invoice);
if(!pdfGenResult.success) {
throw new Error(pdfGenResult.error);
}
return {
...
body: {
filepath: pdfGenResult.filepath
},
};
} catch (e: any) {
...
}
};
This code is all you need to get started, eliminating the need for manual resource creation mentioned in the server section.
An important point to note: We used AWS's Infrastructure as Code (IaC) framework, CDK, to write the infrastructure code. We could have done the same for the server-oriented code mentioned earlier, but it would have been more complex, involving writing a Dockerfile, using a service like Fargate to upload and host the Docker image, and etc.
The IaC code shown here isn't strictly a component of serverless architecture. However, since serverless involves heavily utilizing AWS services, not using an IaC framework would make working with serverless very difficult. If you're interested in learning more about IaC, refer to the end of the document.
Should I care about this as a Business owner?
This version of the passage is well-written and clear. The improvements you've made have addressed the issues in the original text. Here's the passage with minor refinements:
As a business owner, it's crucial to understand these technologies, particularly if your web application is central to your operations.
It's equally important to recognize when serverless isn't the best choice. Depending on your specific requirements, serverless might be costlier compared to alternatives like a Kubernetes deployment solution.
Consider a scenario where your business develops software for parsing and managing legal documents. In this case, you might opt for a container-based infrastructure instead of serverless due to the critical nature of the data being parsed and stored. This approach preserves the flexibility to deploy your code on-premises, which is especially valuable when dealing with sensitive data that cannot be hosted on public cloud infrastructure.
Do you want to learn Serverless?
I‘ve developed a comprehensive Udemy course that guides you through building a Serverless Single Page Application (SPA) from the ground up using AWS, AWS CDK, AWS SDK, Next.js and TypeScript, explaining key concepts as you progress through the class. Prerequisites are limited to basic JavaScript and React knowledge.
Serverless Fullstack with AWS/CDK/NextJS & Typescript.
Check out https://redrobot.dev/ if you need help with a project, consultation or training.