Idyllic
⚡️ An unopinionated language for building APIs ridiculously fast.
Install / Use
/learn @rishiosaur/IdyllicREADME
⚡️ Idyllic
The specification language for building APIs ridiculously fast.
Table of Contents
Getting Started
You can find a tiny example of what Idyllic looks like at the TODO example
Manifesto.
Boilerplate is the single most disingenuous part of the Node ecosystem today. Frameworks like Express, Koa, and so many more focus on having rigid rules for how to write code instead of, well, writing it.
Idyllic aims to reverse that paradigm. Instead of writing your code to follow the arbitrary conventions of a given framework, the Idyllic pattern allows you to write code however you want.
Idyllic wraps Typescript functions in a web server that you define using the Idyllic Language. There aren't any weird functions, conventions, objects, or patterns to recognize; making an Idyllic web service is indistinguishable from writing normal TS.
Language
Idyllic language files are denoted by the extension .idl, and are used to wrap Typescript functions that are
imported from local files like any other JS module. A program written using the Idyllic Language is known as a
singular Idyll, and holds definitions about how data moves through the service & API routes.
define middleware { test, logger } from "./api"
define guards { authed } from "./api"
define handlers { getAllTodos, postTodos } from "./api"
global
| middleware logger
fragment getTodosFragment(level)
| guard authed(level)
| middleware test
route "/todos" {
| middleware test
get {
| expand getTodosFragment("user")
getAll
}
post {
| expand getTodosFragment("admin")
postTodos
}
}
<center><small>A basic example of a todo API implemented using the Idyllic Language</small></center>
Definitions
We begin by importing values from a Typescript or Javascript file. In Idylls, there are three main types of functions that can be imported: handlers, middleware, and guards.
Handlers
Handlers are the simplest of the three types of functions that Idyllic supports, and are quite self-explanatory—they return values based off of a given request.
export const getAllTodos = () => {
return [{
name: "todo 1",
completed: false
}]
}
<center><small>A handler takes in a request and returns a value—this is as simple as it gets!</small></center>
It's also worth noting that Idyllic servers handle serialization for you—you don't need to set any weird headers to make sure that JSON is returned.
You can import handler functions into an Idyll by using define handlers:
define handlers { ... } from '<path>'
Middleware
If you come from nearly any other Node web framework, you'll also be familiar with the concept of middleware. These are used as 'pipes' of data—they intercept an HTTP request and change the data within it somehow, passing it down the chain of middleware and guards (we'll get to this in the next section) until the request hits a handler.
export const loggerMiddleware = (req) => {
log(req)
return {
...req,
logged: true
}
}
<center><small>Middleware intercepts a request and returns some augmented data for the next piece in
the chain.</small></center>
One important thing to note in Idyllic's implementation of middleware is that there is no 'next' function—you just return the arguments that you'd like to pass to the next function in the chain.
You can import middleware functions into an Idyll by using define middleware:
define middleware { ... } from '<path>'
Guards
Guards are also fairly self-explanatory—they guard requests from being executed. You can think of them as 'filters'
for a given request. At a low level, they are functions that take in a request and return a boolean value—if the
guard returns true, then the request continues execution, and vice versa.
export const authGuard = async (req) => {
const { headers: { authorization } } = req
return validateToken(authorization)
}
<center><small>Guards take in a given request and return a boolean value, stopping execution if the result is false.
</small></center>
Unlike middleware, guards are not meant to change the request itself. As such, Idyllic handles context for guards; if the guard allows further execution, the request object that was passed into the guard function will be the same one that is passed into the next function in the chain.
You can import guard functions into an Idyll by using define guards:
define guards { ... } from '<path>'
Sequences
Now that we have functions available to us in the Idyll program through the use of define, how do we go about
using them? Well, Idylls are made up of pipelines of data known as Sequences. A Sequence is just a collection of
guard and middleware functions (and fragments, but we'll get into those later).
Let's annotate the Idyll we saw from before to see where all the Sequences are:
define middleware { test, logger } from "./api"
define guards { authed } from "./api"
define handlers { getAllTodos, postTodos } from "./api"
global
| middleware logger - [ SEQUENCE ]
fragment getTodosFragment(level)
| guard authed(level) ┓
| middleware test ┻ [ SEQUENCE ]
route "/todos" {
| middleware test - [ SEQUENCE ]
get {
| expand getTodosFragment("user") - [ SEQUENCE ]
getAll
}
post {
| expand getTodosFragment("admin") - [ SEQUENCE ]
postTodos
}
}
Each sequence defines how a request moves through the Idyll to reach a handler function (note that handlers are not part of Sequences). You can find them pretty much everywhere in Idylls—they are the bulk of the language.
Sequences can be defined in Fragments, Routes, Request Handlers (separate from handler functions), and the Global Sequence.
The form of a singular Sequence node is | <type> <identifier> (<arguments>).
If we want a given piece of data to flow through a guard called authed, we can write something like:
| guard authed
We may also pass in arguments (arguments can be numbers or strings) to this guard, in case we'd like to customize behaviour (these arguments will get passed into the corresponding function:
| guard authed("user")
The rules for middleware apply here as well, but instead of using the guard keyword, you should use the
middleware keyword.
Where and how can we actually define these sequences, though?
Global Sequence
Oftentimes, you'll want to put some middleware in front of every request that the server handles (like a logger or
an authentication guard). This is where the global keyword comes in.
To define a sequence that gets executed for every single request, you can write one directly under the global name
(note: this is a reserved keyword):
define middleware { test, logger } from "./api"
[...]
global
| middleware logger
[...]
With a program like this, logger will be executed on each request.
Fragments
Writing sequences all over the place can get tiring. That's why the Idyllic Language has first-class macro support!
If you're coming from GraphQL, this bit should come naturally—macros act the exact same way as they do over there. However, Idyllic macros are even more powerful because of their parameter support.
Defining a Fragment is as easy as using the fragment keyword and adding an identifier and your sequence:
To use a Fragment, just include it in any non-Fragment sequence using the expand keyword the same way you would a
Guard or Middleware function:
[...]
fragment GetTodosAuthedAdmin
| guard authed("admin")
| middleware test
fragment GetTodosAuthedUser
| guard authed("user")
| middleware test
route "/todos" {
get {
| expand GetTodosAuthedAdmin
getAll
}
}
route "/todos/{id}" {
get {
| expand GetTodosAuthedUser
getAll
}
}
While this example follows terrible UX, it shows the function of Fragments quite well. In the compilation stage of an Idyll, this program would get expanded to:
[...]
route "/todos" {
get {
| guard authed("admin")
| middleware test
getAll
Related Skills
node-connect
344.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
96.8kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
344.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
344.1kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
