Vixeny
Unleash the functional beast~
Introduction
Vixeny is a purely functional web framework in TypeScript that aims to rival other typed programming languages like Go or Rust.
- High-Performance: Carefully optimized for delivering top-tier performance at every processing level.
- Self-Sufficiency: Operates independently, with no external dependencies.
- Immutable Data: Prioritizes data safety and eliminates side effects.
- Scalability: Ensures high performance even under heavy loads.
- Extensive Testing: Over 100 different tests to ensure stability, reliability, and robustness in various scenarios and environments.
- User-Friendly: Provides clear syntax and comprehensive documentation for developers at all levels.
Benchmarks
Bun
Deno
Get Started in 10 Minutes!
Hello World in Deno!
import { serve } from "https://deno.land/std/http/server.ts";
import vixeny from "https://deno.land/x/endofunctor/fun.ts";
//import vixeny from "npm:vixeny/fun";
await serve(
vixeny({ hasName: "http://127.0.0.1:8080/" })([
{
path: "/",
f: () => "hello world",
},
]),
{ port: 8080, hostname: "127.0.0.1" },
);
Hello World in Bun!
import vixeny from "vixeny/fun";
export default {
port: 8080,
hostname: "127.0.0.1",
fetch: vixeny({ hasName: "http://127.0.0.1:8080/" })([
{
path: "/",
f: () => "hello world",
},
])
}
The Basics
/*
The vixeny framework requires:
vixeny(options)([...petitions])
Options - Configuration options for the vixeny server.
Petitions - An array of Petition (routes).
*/
import { Petitions } from "vixeny/types";
//Petition in the hello world example.
{
path: "/",
f: () => "hello world",
}
Parameters
[
{
path: '/param/:name/bun/:where',
f: (req) => `Hello <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>r</mi><mi>e</mi><mi>q</mi><mi mathvariant="normal">.</mi><mi>p</mi><mi>a</mi><mi>r</mi><mi>a</mi><mi>m</mi><mi mathvariant="normal">.</mi><mi>n</mi><mi>a</mi><mi>m</mi><mi>e</mi></mrow><mo separator="true">,</mo><mi>t</mi><mi>h</mi><mi>e</mi><mi>o</mi><mi>v</mi><mi>e</mi><mi>n</mi><mi>i</mi><mi>s</mi><mi>r</mi><mi>e</mi><mi>a</mi><mi>d</mi><mi>y</mi><mi>f</mi><mi>o</mi><mi>r</mi><mi>y</mi><mi>o</mi><mi>u</mi><mi>a</mi><mi>t</mi><mo>:</mo></mrow><annotation encoding="application/x-tex">{req.param.name}, the oven is ready for you at : </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">re</span><span class="mord mathnormal" style="margin-right:0.03588em;">q</span><span class="mord">.</span><span class="mord mathnormal">p</span><span class="mord mathnormal">a</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">am</span><span class="mord">.</span><span class="mord mathnormal">nam</span><span class="mord mathnormal">e</span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">t</span><span class="mord mathnormal">h</span><span class="mord mathnormal">eo</span><span class="mord mathnormal" style="margin-right:0.03588em;">v</span><span class="mord mathnormal">e</span><span class="mord mathnormal">ni</span><span class="mord mathnormal">sre</span><span class="mord mathnormal">a</span><span class="mord mathnormal">d</span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mord mathnormal">oryo</span><span class="mord mathnormal">u</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>{req.param.where}`
},
{
path: '/param/:name/deno/:where',
f: (req) => `Hello <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>r</mi><mi>e</mi><mi>q</mi><mi mathvariant="normal">.</mi><mi>p</mi><mi>a</mi><mi>r</mi><mi>a</mi><mi>m</mi><mi mathvariant="normal">.</mi><mi>n</mi><mi>a</mi><mi>m</mi><mi>e</mi></mrow><mo separator="true">,</mo><mi>t</mi><mi>i</mi><mi>m</mi><mi>e</mi><mi>t</mi><mi>o</mi><mi>r</mi><mi>i</mi><mi>d</mi><mi>e</mi><mi>t</mi><mi>h</mi><mi>e</mi><mi>t</mi><mo>−</mo><mi>r</mi><mi>e</mi><mi>x</mi><mi>a</mi><mi>t</mi><mo>:</mo></mrow><annotation encoding="application/x-tex">{req.param.name}, time to ride the t-rex at : </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">re</span><span class="mord mathnormal" style="margin-right:0.03588em;">q</span><span class="mord">.</span><span class="mord mathnormal">p</span><span class="mord mathnormal">a</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">am</span><span class="mord">.</span><span class="mord mathnormal">nam</span><span class="mord mathnormal">e</span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">t</span><span class="mord mathnormal">im</span><span class="mord mathnormal">e</span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.02778em;">or</span><span class="mord mathnormal">i</span><span class="mord mathnormal">d</span><span class="mord mathnormal">e</span><span class="mord mathnormal">t</span><span class="mord mathnormal">h</span><span class="mord mathnormal">e</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mord mathnormal">re</span><span class="mord mathnormal">x</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>{req.param.where}`
},
]
Query
[
{
path: '/query/name',
f: (req) => `Your name is : ${req.query?.name || "default"}`
},
{
path: '/query/nameAndId',
f: (req) => `Your name is : <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>r</mi><mi>e</mi><mi>q</mi><mi mathvariant="normal">.</mi><mi>q</mi><mi>u</mi><mi>e</mi><mi>r</mi><mi>y</mi><mo stretchy="false">?</mo><mi mathvariant="normal">.</mi><mi>n</mi><mi>a</mi><mi>m</mi><mi>e</mi><mi mathvariant="normal">∣</mi><msup><mi mathvariant="normal">∣</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mi>d</mi><mi>e</mi><mi>f</mi><mi>a</mi><mi>u</mi><mi>l</mi><msup><mi>t</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mrow><mi>w</mi><mi>i</mi><mi>t</mi><mi>h</mi><mi>t</mi><mi>h</mi><mi>e</mi><mi>i</mi><mi>d</mi><mo>:</mo></mrow><annotation encoding="application/x-tex">{req.query?.name || 'default'} with the id : </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.0019em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathnormal">re</span><span class="mord mathnormal" style="margin-right:0.03588em;">q</span><span class="mord">.</span><span class="mord mathnormal" style="margin-right:0.03588em;">q</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.03588em;">ery</span><span class="mclose">?</span><span class="mord">.</span><span class="mord mathnormal">nam</span><span class="mord mathnormal">e</span><span class="mord">∣</span><span class="mord"><span class="mord">∣</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mord mathnormal">d</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mord mathnormal">a</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span><span class="mord mathnormal">i</span><span class="mord mathnormal">t</span><span class="mord mathnormal">h</span><span class="mord mathnormal">t</span><span class="mord mathnormal">h</span><span class="mord mathnormal">e</span><span class="mord mathnormal">i</span><span class="mord mathnormal">d</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>{req.query?.id || "-1"}`
},
// Only the 'name' field is parsed from the query, ignoring other fields,
// using "only" improves significantly the performance.
{
path: '/query/onlyName',
query: {
only: ["name"]
},
f: (req) => `Hello ${req.query?.name || 'default'}`
},
]
Options
[
// This petition has the pre-set ".html" header that sets to "text/html".
{
path: "/headers/hello",
headers: ".html",
f: () => "<p>Hello world!</p>"
},
// Headers can also be manually set using an object. Here,
// Here, "Content-Type" is manually set to "text/html".
{
path: "/headers/again",
headers: { "Content-Type": "text/html"},
f: () => "<p>Hello world!</p>"
},
// This route mirrors the body of the request.
// Here, the "POST" method is used.
{
path: "/headers/method",
method: "POST", // "GET" | "HEAD" | "POST" | "DELETE"
status: 201,
f: (request) => request.req.body
}
]
Type Request
/*
* By setting the type to "request", the return type is changed from BodyInit to Response.
*/
[
// The ":name" is a dynamic part of the route which will match any string.
// The function 'f' checks if the 'name' parameter equals "Bun" or "Deno".
// If so , it responds with a 200 status code.
// If 'name' is anything else, it responds with a 400.
{
path: "/response/who/:name",
type: "request",
f: (req) => (req.param.name === "Bun" || req.param.name === "Deno")
? new Response( "Welcome", {status:200})
: new Response( "Only devs here", {status: 400})
}
]
Vixeny
Offers the ability to manage different aspects of an HTTP request-response cycle in the arguments as response, including headers, response status, request methods, query , parameters an others.
- add: allows you to add fields to the arguments, effectively enabling parsing for certain aspects of the request when your arguments go out of the scope
import functionX from "outOfScope"
[
{
//(f) will have the fields "req" and "param"
path:"/example/:id",
add: ["req","param"],
f: f => functionX(f)
}
]
- delete: allows you to remove fields from the arguments, providing the ability to exclude certain aspects of the request when there are no needed.
[
{
path: '/query/block',
delete: ['query'],
f: () => "Hello world"
}
]
Vixeny will always try to give you only what is necessary, not only for optimization, but also for the sake of safety and to avoid side effects.
Type Response
/*
* When the type is set to "response", the optimizer is bypassed.
* In such case, a Request is received and a Response is returned directly.
* Note that "r" is used instead of "f"
*/
[
// The route has the type set to "response", bypassing the optimizer.
// The function 'r' directly takes a Request object and returns a Response object.
{
path: "/response/hello",
type: "response",
r: (request) => new Response("Hello world!")
}
]
Type Static
/*
* The "path" is relative to where the terminal is currently located.
* In this example, we're serving files from the "misc" directory.
* MIME types are enabled by default but can be disabled by setting "mime: false".
* At the moment, only one static file can be served at a time.
*/
[
{
type: "static",
name: "/static/",
path: "./misc/",
},
]
Join all together
In the Vixeny framework, the spread operator (...
) can be used to efficiently include or import routes from other modules or parts of your application. This technique simplifies code organization and encourages a modular design.
The example you provided demonstrates the use of the spread operator in the main route configuration:
[
// A simple "Hello World" petition
{
path: "/",
f: () => "Hello world"
},
// Petitions imported from other modules using the spread operator
...urlParams,
...urlQueries,
...httpOptions,
...typeRequest,
...typeResponse,
...staticServer,
...jsonStringifier
]
In this configuration array, urlParams
, urlQueries
, httpOptions
, typeRequest
, typeResponse
, staticServer
, and jsonStringifier
are arrays of routes that are defined elsewhere in your application. By using the spread operator, these petitions definitions are unpacked and included directly.
Stringifier
/*
* This method uses the use of JSON Schema for faster serialization.
* By defining the structure of our data, we can potentially speed up JSON.stringify by a factor of 3 or more.
*/
[
// The function 'f' returns the parameters of the request.
// The 'json' attribute is used to define a JSON Schema that describes the structure of the expected JSON data.
{
path: "/json/:name",
f: (req) => req.param,
json: {
scheme: {
type: "object",
properties:{
name: {
type: "string"
}
},
required: ["name"]
}
}
}
]
Wildcard
//assuming we have these petitions
[
{path: "/hello/*", f: () => "first"},
{path: "/hello/nested/*" , f: () => "second"}
]
// new Request("http://127.0.0.1:8080/hello/nested/hi") -> "second"
// new Request("http://127.0.0.1:8080/hello/hi") -> "first"
Vixeny’s contexts
// http://127.0.0.1:3000/context/hello
vixeny({
hasName: 'http://127.0.0.1:3000/'
})([{
path: "/",
type: "response",
r: () => new Response("hi")
},
{
path: "/context/*",
type: "response",
//this vixeny context do not share anything with its caller
r: vixeny({
hasName: 'http://127.0.0.1:3000/context/'
})([{
path: "/hello",
f: () => "hello from this context"
}
])
}
])
Thank you and enjoy using Vixeny!
Experimental Features
These features are still under development and may change over time.
Modularity
Compose and work as you want with components:
import stringify from "vixeny/components/stringify/stringify"
//Experimental
import signer from "vixeny/components/tokens/signer"
import verifier from "vixeny/components/tokens/verifier"
Q&A
Why doesn’t Vixeny support Node.js?
Node.js is not supported due to its incompatibility with the Response and Request methods used by Vixeny.