Skip to content
当前有符合你浏览器所设置语言的版本,是否前往zh-CN版本的网站?
There is a version suitable for your browser's language settings. Would you like to go to the zh-CN language of the site?
HomeDocument

Introduction

H1

Milkio is a TypeScript backend framework, supporting Bun, and in Serverless, it has faster speed.

It aims to minimize frustration and pain during development through elegant design, wrapping complex concepts, and deep integration with editors.

How does it achieve that?

Many times, as long as we don’t consider some options that are almost irrelevant to us, we will find that life can become so much easier.

In Milkio, we cleverly encapsulate the complexity of past development. This allows you to create your server in a very simple and intuitive way.

But don’t worry, it doesn’t mean that some things that can be done in traditional development are impossible in Milkio. In Milkio, we empower you with the ability to easily control any details so that you can accomplish anything you want.

Forget complexity of I/O

In Milkio, there are no complex controllers or annotations. You only need to write a simple method and place it in the apps directory. It can be directly called by using a URL that matches its input and its path. Your method is type-safe, and any input that doesn’t conform to the expected types will be rejected, and any extra properties that meet the type requirements will be removed. Of course, we apply some “magic 🪄” behind the scenes, but for you, writing an API is that simple.

Why not try a simple greeting to the world…?

/**
* This is an API that greets the world!
* These ~~comments~~ will be rendered in the **Cookbook**
*/
export const api = defineApi({
meta: {},
async action(params: string) {
return `Hello, world! (from ${params})`;
},
});

You can call it in your client code like this, without the need for Axios or Fetch. You can easily build your own “Axios”. When you enter the path, TypeScript’s auto-completion will help you:

const result = await client.execute("hello-world/say", "🥛 Milkio");
if (!result.success) return alert(result.message);
console.log(result.data); // Output: "Hello, world! (from 🥛 Milkio)"

Forget JavaScript

In Milkio, you can only use TypeScript, and that’s a good thing!

Milkio leverages TypeScript’s type system to enforce type safety for your APIs, as described earlier. Although it requires you to write all your code in TypeScript, it’s a very worthwhile trade-off.

Milkio’s type system is carefully designed to automatically infer types in your code, allowing you to enjoy full autocompletion. Thanks to the emphasis on the type system in Milkio’s design, you can easily go from defining entities for the database to validating your API’s parameters with writing zero to one types!

End-to-end type safety

Every Milkio project automatically creates a client package. In other servers or clients, simply using this package allows you to call your API, complete with all the types and the magical autocompletion, like this:

Unlike tRPC and other frameworks, it is an independent npm package that belongs entirely to your project. It doesn’t force you to compose all projects into a monorepo, and it doesn’t leak your source code. Its internals only contain types specific to your project.

You can publish it to a public or private npm repository or install it locally in another project. You can also add lifecycle methods to it, such as automatically updating tokens or calculating signatures before each request, making your package even more user-friendly.

Freedom to switch runtimes

We have the freedom to switch runtimes thanks to Milkio’s compliance with the WinterCG standard. All requests and responses in Milkio are standard Request and Response objects, and all requests are sent using Fetch, which conforms to the WinterCG standard. There is no code dependency on Node.js or Bun.

This means that Milkio can run not only in Node.js and Bun but also in any other places like Cloudflare Worker, Vercel Edge Function, and more.

We intentionally didn’t wrap any commands, so when you read the package.json, you won’t find something like milkio start, but rather bun ./run-serve.ts. When you want to use a runtime other than Bun, you only need to replace the corresponding commands.

Furthermore, we have implemented development-related features such as Watch, Generate Plugins, and Test, which are runtime-independent and provided through a VS Code extension. This ensures that we can easily switch runtimes without being tied to anyone.

Cookbook

Cookbook is a documentation tool for Milkio that analyzes your code and generates online documentation based on comments. The request and response parts are automatically inferred and don’t require you to write any explanations. It is currently in the alpha stage, and in the future, we will introduce more features to Cookbook to help you build your dreams more pleasantly.

Serverless

In the era of Serverless, the rules of the game have changed.

In the past, our code ran on a single server, which usually had many CPU cores and a large amount of memory. Now, our code often runs in individual Pods, each with only one or fewer CPU cores (yes, some Serverless providers even allow you to choose as little as 0.05 cores) and limited memory. However, as the number of requests increases, more Pods are created.

Previously, creating multiple worker threads was a good way to fully utilize hardware resources. The framework runtime wouldn’t execute specific code but would create worker threads equal to the number of CPU cores. However, now it becomes a burden, and even creating just one worker thread has a significant impact on cold start speed.

Cold start speed is crucial. When a new request arrives and there are no Pods available for reuse, Serverless providers will spin up a new Pod for you.

Milkio utilizes the generation phase to pre-compile your routes, type-safe code, and more into static code. This speeds up the startup of our application, similar to what AOT does.

Cold startup speed
- Milkio + Bun (1 APIs)
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2.29ms (1x)
- Milkio + Bun (1024 APIs)
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2.32ms (1.01x)
- NestJS + Bun (1 APIs)
███████████████░░░░░░░░░░░░░░░░░░░░░░░░░ 268.53ms (117.26x)
- NestJS + Bun (1024 APIs)
████████████████████████████████████████ 713.13ms (311.41x)
Use Macbook Pro 2023

In addition, each API is lazily loaded. In practice, only a few APIs are popular, while more APIs are less frequently used. Since Pods have a short lifecycle, loading all APIs in memory during startup, as we did in the past, would not only result in long startup times but also waste a lot of memory. Milkio only loads an API when it is actually called.

Memory usage
(after the initial invocation of the interface following startup)
- Milkio + Bun (1 APIs)
███░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 6.02 MB (1x)
- Milkio + Bun (1024 APIs)
████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 6.39 MB (1.06x)
- NestJS + Bun (1 APIs)
██████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 18.04 MB (3.00x)
- NestJS + Bun (1024 APIs)
████████████████████████████████████████ 72.11 MB (11.97x)
Use Macbook Pro 2023

Prerequisite Knowledge

Milkio is a framework that strives to be simple and easy to understand in design, but it still requires you to have a basic understanding of TypeScript syntax (variables, functions, conditional statements, loops, closures, modules) and type system (basic types, union types, generics). If you are not familiar with all of these topics yet, take some time to learn them first! There are plenty of excellent and free video courses available on Youtube.

Some questions?

Feel free to join my Discord! I and other Milkio users are more than willing to help you with any questions you may have!