= {
2 | $nixleTypeError: T;
3 | };
4 | export type ValidPath = P extends '/' ? P : P extends `/${string}` ? P extends `${string}/` ? NixleTypeError<'Path must not end with /'> & string : P : NixleTypeError<'Path must start with /'> & string;
5 |
--------------------------------------------------------------------------------
/packages/elysia/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../configs/tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "./dist",
5 | "rootDir": "./src",
6 | "baseUrl": "./src",
7 | "paths": {
8 | "~/*": ["./*"]
9 | }
10 | },
11 | "include": ["./src/**/*"],
12 | "exclude": ["node_modules", "dist"]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/express/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../configs/tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "./dist",
5 | "rootDir": "./src",
6 | "baseUrl": "./src",
7 | "paths": {
8 | "~/*": ["./*"]
9 | }
10 | },
11 | "include": ["./src/**/*"],
12 | "exclude": ["node_modules", "dist"]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/fastify/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../configs/tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "./dist",
5 | "rootDir": "./src",
6 | "baseUrl": "./src",
7 | "paths": {
8 | "~/*": ["./*"]
9 | }
10 | },
11 | "include": ["./src/**/*"],
12 | "exclude": ["node_modules", "dist"]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/hono/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../configs/tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "./dist",
5 | "rootDir": "./src",
6 | "baseUrl": "./src",
7 | "paths": {
8 | "~/*": ["./*"]
9 | }
10 | },
11 | "include": ["./src/**/*"],
12 | "exclude": ["node_modules", "dist"]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/nitro/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../configs/tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "./dist",
5 | "rootDir": "./src",
6 | "baseUrl": "./src",
7 | "paths": {
8 | "~/*": ["./*"]
9 | }
10 | },
11 | "include": ["./src/**/*"],
12 | "exclude": ["node_modules", "dist"]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/ofetch/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../configs/tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "./dist",
5 | "rootDir": "./src",
6 | "baseUrl": "./src",
7 | "paths": {
8 | "~/*": ["./*"]
9 | }
10 | },
11 | "include": ["./src/**/*"],
12 | "exclude": ["node_modules", "dist"]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/zod/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../configs/tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "./dist",
5 | "rootDir": "./src",
6 | "baseUrl": "./src",
7 | "paths": {
8 | "~/*": ["./*"]
9 | }
10 | },
11 | "include": ["./src/**/*"],
12 | "exclude": ["node_modules", "dist"]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/nitro/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | import type { IncomingMessage, ServerResponse } from 'http';
2 | import type { NitroApp } from 'nitropack';
3 | export interface Request extends IncomingMessage {
4 | }
5 | export interface Response extends ServerResponse {
6 | }
7 | export declare const nitroProvider: (app: NitroApp) => import("nixle").Provider;
8 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "docs:dev": "vitepress dev",
4 | "docs:build": "vitepress build",
5 | "docs:preview": "vitepress preview"
6 | },
7 | "devDependencies": {
8 | "sass": "^1.69.5",
9 | "vitepress": "^1.0.0-rc.31"
10 | },
11 | "dependencies": {
12 | "@vercel/analytics": "^1.1.1",
13 | "vue-router": "^4.2.5"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/nixle/src/utils/types.ts:
--------------------------------------------------------------------------------
1 | export type NixleTypeError = {
2 | $nixleTypeError: T;
3 | };
4 |
5 | export type ValidPath = P extends '/'
6 | ? P
7 | : P extends `/${string}`
8 | ? P extends `${string}/`
9 | ? NixleTypeError<'Path must not end with /'> & string
10 | : P
11 | : NixleTypeError<'Path must start with /'> & string;
12 |
--------------------------------------------------------------------------------
/packages/nixle/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../configs/tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "./dist",
5 | "rootDir": "./src",
6 | "baseUrl": "./src",
7 | "paths": {
8 | "~/*": ["./*"]
9 | },
10 | "typeRoots": ["./node_modules/@types", "./src/global.d.ts"]
11 | },
12 | "include": ["./src/**/*"],
13 | "exclude": ["node_modules", "dist"]
14 | }
15 |
--------------------------------------------------------------------------------
/configs/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2021",
4 | "moduleResolution": "Bundler",
5 | "allowImportingTsExtensions": true,
6 | "declaration": true,
7 | "emitDeclarationOnly": true,
8 | "verbatimModuleSyntax": true,
9 | "esModuleInterop": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "strict": true,
12 | "skipLibCheck": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/nixle/src/utils/validations.ts:
--------------------------------------------------------------------------------
1 | import { StatusCode, createError } from '..';
2 |
3 | export function validatePath(path: string) {
4 | if (!path.startsWith('/')) {
5 | throw createError('Path must start with /', StatusCode.INTERNAL_SERVER_ERROR);
6 | }
7 |
8 | if (path.length > 1 && path.endsWith('/')) {
9 | throw createError('Path must not end with /', StatusCode.INTERNAL_SERVER_ERROR);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/nixle/dist/createModule.d.ts:
--------------------------------------------------------------------------------
1 | import type { Router } from './router';
2 | export interface ModuleOptions {
3 | routers: R;
4 | }
5 | export interface Module {
6 | routers: R;
7 | name: Lowercase;
8 | }
9 | export declare function createModule(name: Lowercase, options: ModuleOptions): Module;
10 |
--------------------------------------------------------------------------------
/packages/nixle/dist/createGuard.d.ts:
--------------------------------------------------------------------------------
1 | import { type RouteHandlerContext } from '.';
2 | import { type log } from './logger';
3 | export interface GuardFunction {
4 | (context: RouteHandlerContext & {
5 | log: typeof log;
6 | }): Promise | void;
7 | }
8 | export interface Guard {
9 | (context: RouteHandlerContext): Promise | void;
10 | }
11 | export declare function createGuard(name: Lowercase, guard: GuardFunction): Guard;
12 |
--------------------------------------------------------------------------------
/packages/hono/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | import type { Hono, Context } from 'hono';
2 | type HonoRequest = Context['req'];
3 | type HonoResponse = Context['res'];
4 | export interface Request extends HonoRequest {
5 | }
6 | export interface Response extends HonoResponse {
7 | }
8 | export declare const honoProvider: (app: Hono) => import("nixle").Provider>;
9 | export {};
10 |
--------------------------------------------------------------------------------
/packages/nixle/src/provider/createProvider.ts:
--------------------------------------------------------------------------------
1 | import type { HTTPMethod } from '~/types/HTTPMethod';
2 | import type { ProviderRouteHandler } from './RouteHandler';
3 |
4 | export interface Provider {
5 | app: T;
6 | createRoute: (params: {
7 | method: Lowercase;
8 | path: string;
9 | handler: ProviderRouteHandler;
10 | }) => void;
11 | }
12 |
13 | export function createProvider(config: (app: T) => Provider) {
14 | return config;
15 | }
16 |
--------------------------------------------------------------------------------
/packages/nixle/dist/provider/createProvider.d.ts:
--------------------------------------------------------------------------------
1 | import type { HTTPMethod } from '../types/HTTPMethod';
2 | import type { ProviderRouteHandler } from './RouteHandler';
3 | export interface Provider {
4 | app: T;
5 | createRoute: (params: {
6 | method: Lowercase;
7 | path: string;
8 | handler: ProviderRouteHandler;
9 | }) => void;
10 | }
11 | export declare function createProvider(config: (app: T) => Provider): (app: T) => Provider;
12 |
--------------------------------------------------------------------------------
/packages/nixle/dist/createMiddleware.d.ts:
--------------------------------------------------------------------------------
1 | import { type RouteHandlerContext } from '.';
2 | import { type log } from './logger';
3 | export interface MiddlewareFunction {
4 | (context: RouteHandlerContext & {
5 | log: typeof log;
6 | }): Promise | void;
7 | }
8 | export interface Middleware {
9 | (context: RouteHandlerContext): Promise | void;
10 | }
11 | export declare function createMiddleware(name: Lowercase, middleware: MiddlewareFunction): Middleware;
12 |
--------------------------------------------------------------------------------
/examples/nuxt/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nuxt-app",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "build": "nuxt build",
7 | "dev": "nuxt dev",
8 | "generate": "nuxt generate",
9 | "preview": "nuxt preview",
10 | "postinstall": "nuxt prepare"
11 | },
12 | "devDependencies": {
13 | "nuxt": "^3.11.1",
14 | "vue": "^3.4.21",
15 | "vue-router": "^4.3.0"
16 | },
17 | "dependencies": {
18 | "@nixle/nitro": "workspace:^",
19 | "nixle": "workspace:^"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/nixle/src/createModule.ts:
--------------------------------------------------------------------------------
1 | import type { Router } from '~/router';
2 |
3 | export interface ModuleOptions {
4 | routers: R;
5 | }
6 |
7 | export interface Module {
8 | routers: R;
9 | name: Lowercase;
10 | }
11 |
12 | export function createModule(
13 | name: Lowercase,
14 | options: ModuleOptions,
15 | ): Module {
16 | return { name, ...options };
17 | }
18 |
--------------------------------------------------------------------------------
/packages/ofetch/src/index.ts:
--------------------------------------------------------------------------------
1 | import { createPlugin } from 'nixle';
2 | import { ofetch, type FetchOptions, type $Fetch } from 'ofetch';
3 |
4 | declare global {
5 | namespace Nixle {
6 | interface ServiceContext {
7 | ofetch: $Fetch;
8 | }
9 | }
10 | }
11 |
12 | export const ofetchPlugin = (options?: FetchOptions) => {
13 | const _ofetch = ofetch.create(options || {});
14 |
15 | return createPlugin('ofetch', ({ extendServiceContext }) => {
16 | extendServiceContext({ ofetch: _ofetch });
17 | });
18 | };
19 |
--------------------------------------------------------------------------------
/packages/nixle/dist/utils/helpers.d.ts:
--------------------------------------------------------------------------------
1 | export declare const pick: , K extends keyof O>(obj: O, fields: K[]) => Pick;
2 | export declare const exclude: , K extends keyof O>(obj: O, fields: K[]) => Exclude;
3 | export declare const isPrimitive: (val: any) => boolean;
4 | export declare const joinPath: (...paths: string[]) => string;
5 | export declare const tryToParse: (value: string) => any;
6 | export declare const parseObject: (obj: Record) => Record;
7 |
--------------------------------------------------------------------------------
/docs/introduction/roadmap.md:
--------------------------------------------------------------------------------
1 | # Roadmap
2 |
3 | This is a list of features that are planned for Nixle before the 1.0 release.
4 |
5 | - ✅ Add plugin system
6 | - ✅ Create HTTP plugin
7 | - ✅ Add validation for query and body
8 | - ✅ Add support for middlewares
9 | - ✅ Add support for guards
10 | - Add all response types (stream, file, etc)
11 | - Integrate with Next.js
12 | - Add CLI for generating code
13 | - Add support for GraphQL
14 | - Add support for WebSockets
15 | - Add support for Swagger
16 | - Integrate CORS
17 | - Add REST client generator
18 |
--------------------------------------------------------------------------------
/scripts/patch-versions.ts:
--------------------------------------------------------------------------------
1 | import { readdirSync, writeFileSync } from 'node:fs';
2 | import { readJSONSync } from 'fs-extra';
3 | import path from 'node:path';
4 |
5 | const nixleVersion = readJSONSync('./package.json').version;
6 | const packages = readdirSync('./packages');
7 |
8 | for (const _package of packages) {
9 | const dir = path.join('./packages', _package);
10 | const pkgPath = path.join(dir, './package.json');
11 | const pkg = readJSONSync(pkgPath);
12 |
13 | pkg.version = nixleVersion;
14 |
15 | writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
16 | }
17 |
--------------------------------------------------------------------------------
/packages/nixle/src/plugins/buildPlugins.ts:
--------------------------------------------------------------------------------
1 | import type { AppOptions } from '~/createApp';
2 | import { contextLog } from '~/logger';
3 | import { extendRouterContext } from '~/router/createRouter';
4 | import { extendServiceContext } from '~/createService';
5 | import type { Provider } from '..';
6 |
7 | export const buildPlugins = (provider: Provider, options: AppOptions) => {
8 | if (!options.plugins) {
9 | return;
10 | }
11 |
12 | options.plugins.forEach(({ name, plugin }) => {
13 | const log = contextLog(name, 'bgMagenta');
14 |
15 | plugin({ provider, log, extendRouterContext, extendServiceContext });
16 | });
17 | };
18 |
--------------------------------------------------------------------------------
/examples/nuxt/server/plugins/nixle.ts:
--------------------------------------------------------------------------------
1 | import { createApp, createRouter, createService } from 'nixle';
2 | import { nitroProvider } from '@nixle/nitro';
3 |
4 | const usersService = createService('users', ({ log }) => {
5 | const create = async () => {
6 | log.info('Creating user');
7 |
8 | return {};
9 | };
10 |
11 | return {
12 | create,
13 | };
14 | });
15 |
16 | const usersRouter = createRouter('/users', ({ route }) => [
17 | route.get('/', () => usersService().create()),
18 | ]);
19 |
20 | export default defineNitroPlugin((nitroApp) => {
21 | createApp({
22 | provider: nitroProvider(nitroApp),
23 | routers: [usersRouter],
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/examples/elysia/index.ts:
--------------------------------------------------------------------------------
1 | import { Elysia } from 'elysia';
2 | import { createApp, createRouter, createService } from 'nixle';
3 | import { elysiaProvider } from '@nixle/elysia';
4 |
5 | const usersService = createService('users', ({ log }) => {
6 | const create = async () => {
7 | log.info('Creating user');
8 |
9 | return {};
10 | };
11 |
12 | return {
13 | create,
14 | };
15 | });
16 |
17 | const usersRouter = createRouter('/users', ({ route }) => [
18 | route.get('/', () => usersService().create()),
19 | ]);
20 |
21 | const { app } = createApp({
22 | provider: elysiaProvider(new Elysia()),
23 | routers: [usersRouter],
24 | });
25 |
26 | app.listen(4000);
27 |
--------------------------------------------------------------------------------
/examples/express/index.mjs:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import { createApp, createRouter, createService } from 'nixle';
3 | import { expressProvider } from '@nixle/express';
4 |
5 | const usersService = createService('users', ({ log }) => {
6 | const create = async () => {
7 | log.info('Creating user');
8 |
9 | return {};
10 | };
11 |
12 | return {
13 | create,
14 | };
15 | });
16 |
17 | const usersRouter = createRouter('/users', ({ route }) => [
18 | route.get('/', () => usersService().create()),
19 | ]);
20 |
21 | const { app } = createApp({
22 | provider: expressProvider(express()),
23 | routers: [usersRouter],
24 | });
25 |
26 | app.listen(4000);
27 |
--------------------------------------------------------------------------------
/examples/fastify/index.mjs:
--------------------------------------------------------------------------------
1 | import fastify from 'fastify';
2 | import { createApp, createRouter, createService } from 'nixle';
3 | import { fastifyProvider } from '@nixle/fastify';
4 |
5 | const usersService = createService(({ log }) => {
6 | const create = async () => {
7 | log.info('Creating user');
8 |
9 | return {};
10 | };
11 |
12 | return {
13 | create,
14 | };
15 | });
16 |
17 | const usersRouter = createRouter('/users', ({ route }) => [
18 | route.get('/', () => usersService().create()),
19 | ]);
20 |
21 | const { app } = createApp({
22 | provider: fastifyProvider(fastify()),
23 | routers: [usersRouter],
24 | });
25 |
26 | app.listen({ port: 4000 });
27 |
--------------------------------------------------------------------------------
/packages/fastify/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | import type { IncomingMessage, ServerResponse } from 'http';
2 | import type { FastifyInstance } from 'fastify';
3 | export interface Request extends IncomingMessage {
4 | }
5 | export interface Response extends ServerResponse {
6 | }
7 | export declare const fastifyProvider: (app: FastifyInstance, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault>) => import("nixle").Provider, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault>>;
8 |
--------------------------------------------------------------------------------
/packages/nixle/dist/plugins/createPlugin.d.ts:
--------------------------------------------------------------------------------
1 | import type { extendRouterContext } from '../router/createRouter';
2 | import type { extendServiceContext } from '../createService';
3 | import type { log } from '../logger';
4 | import type { Provider } from '..';
5 | interface PluginOptions {
6 | provider: Provider;
7 | log: typeof log;
8 | extendRouterContext: typeof extendRouterContext;
9 | extendServiceContext: typeof extendServiceContext;
10 | }
11 | type PluginFunction = (options: PluginOptions) => void;
12 | export interface Plugin {
13 | name: string;
14 | plugin: PluginFunction;
15 | }
16 | export declare const createPlugin: (name: string, plugin: PluginFunction) => Plugin;
17 | export {};
18 |
--------------------------------------------------------------------------------
/packages/nixle/src/plugins/createPlugin.ts:
--------------------------------------------------------------------------------
1 | import type { extendRouterContext } from '~/router/createRouter';
2 | import type { extendServiceContext } from '~/createService';
3 | import type { log } from '../logger';
4 | import type { Provider } from '..';
5 |
6 | interface PluginOptions {
7 | provider: Provider;
8 | log: typeof log;
9 | extendRouterContext: typeof extendRouterContext;
10 | extendServiceContext: typeof extendServiceContext;
11 | }
12 |
13 | type PluginFunction = (options: PluginOptions) => void;
14 |
15 | export interface Plugin {
16 | name: string;
17 | plugin: PluginFunction;
18 | }
19 |
20 | export const createPlugin = (name: string, plugin: PluginFunction): Plugin => ({
21 | name,
22 | plugin,
23 | });
24 |
--------------------------------------------------------------------------------
/packages/nixle/src/env.ts:
--------------------------------------------------------------------------------
1 | import dotenv from 'dotenv';
2 | import type { RouteHandlerContext } from './router';
3 | import { StatusCode, createError } from '.';
4 |
5 | const env: Nixle.Env = {};
6 |
7 | export const getEnv = (): RouteHandlerContext['env'] => ({
8 | ...env,
9 | get: (key) => env[key],
10 | getOrThrow(key) {
11 | const value = env[key];
12 |
13 | if (value === undefined) {
14 | throw createError(`Env variable "${key}" is required`, StatusCode.INTERNAL_SERVER_ERROR);
15 | }
16 |
17 | return value;
18 | },
19 | });
20 |
21 | export const buildEnv = (config?: dotenv.DotenvConfigOptions) => {
22 | dotenv.config(config);
23 |
24 | Object.keys(process.env).forEach((key) => {
25 | env[key] = process.env[key];
26 | });
27 | };
28 |
--------------------------------------------------------------------------------
/examples/hono/index.mjs:
--------------------------------------------------------------------------------
1 | import { Hono } from 'hono';
2 | import { serve } from '@hono/node-server';
3 | import { createApp, createRouter, createService } from 'nixle';
4 | import { honoProvider } from '@nixle/hono';
5 |
6 | const usersService = createService('users', ({ log }) => {
7 | const create = async () => {
8 | log.info('Creating user');
9 |
10 | return {};
11 | };
12 |
13 | return {
14 | create,
15 | };
16 | });
17 |
18 | const usersRouter = createRouter('/users', ({ route }) => [
19 | route.get('/', () => usersService().create()),
20 | ]);
21 |
22 | const { app } = createApp({
23 | provider: honoProvider(new Hono()),
24 | routers: [usersRouter],
25 | });
26 |
27 | serve(app, (info) => {
28 | console.log(`Listening on http://localhost:${info.port}`);
29 | });
30 |
--------------------------------------------------------------------------------
/packages/nixle/dist/createService.d.ts:
--------------------------------------------------------------------------------
1 | import { type RouteHandlerContext } from '.';
2 | import { type log } from './logger';
3 | export declare const extendServiceContext: (options: T) => void;
4 | interface ServiceContext extends Nixle.ServiceContext {
5 | log: typeof log;
6 | env: RouteHandlerContext['env'];
7 | }
8 | interface ServiceFunction {
9 | (context: ServiceContext): M;
10 | }
11 | export interface Service any> = Record any>> {
12 | (): M;
13 | }
14 | export declare function createService any> = Record any>>(name: Lowercase, methods: ServiceFunction): Service;
15 | export {};
16 |
--------------------------------------------------------------------------------
/docs/plugins/cors.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # CORS
6 |
7 | In nearest future, we won't implement CORS support as separate plugin. Instead, you should use CORS plugin depends on framework you use.
8 |
9 | ## Example
10 |
11 | For example if you use [Fastify](/providers/fastify) provider, you can use [@fastify/cors](https://github.com/fastify/fastify-cors) plugin:
12 |
13 | ```ts
14 | import fastify from 'fastify';
15 | import cors from '@fastify/cors';
16 | import { createApp } from 'nixle';
17 | import { fastifyProvider } from '@nixle/fastify';
18 |
19 | const fastifyApp = fastify();
20 |
21 | fastifyApp.register(cors, {
22 | // CORS options
23 | });
24 |
25 | const { app } = createApp({
26 | provider: fastifyProvider(fastifyApp),
27 | });
28 |
29 | app.listen({ port: 3000 });
30 | ```
31 |
--------------------------------------------------------------------------------
/configs/vite.config.base.ts:
--------------------------------------------------------------------------------
1 | import { UserConfig } from 'vite';
2 |
3 | export const viteConfig = ({
4 | name,
5 | entry,
6 | package: _package,
7 | }: {
8 | name: string;
9 | entry: string;
10 | package: Record;
11 | }): UserConfig => ({
12 | resolve: {
13 | alias: {
14 | '~': '/src',
15 | },
16 | },
17 | build: {
18 | target: 'node18',
19 | lib: {
20 | entry,
21 | name,
22 | fileName: 'index',
23 | formats: ['es'],
24 | },
25 | rollupOptions: {
26 | external: [
27 | 'consola/utils',
28 | ...Object.keys(_package.dependencies || {}),
29 | ...Object.keys(_package.devDependencies || {}),
30 | ...Object.keys(_package.peerDependencies || {}),
31 | /^node:.*/,
32 | ],
33 | },
34 | },
35 | });
36 |
--------------------------------------------------------------------------------
/packages/nixle/src/index.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | namespace Nixle {
3 | interface RouterContext {}
4 | interface ServiceContext {}
5 | interface Env extends Record {}
6 | }
7 | }
8 |
9 | export type { HTTPMethod } from '~/types/HTTPMethod';
10 | export type { CookieOptions } from '~/types/CookieOptions';
11 | export { StatusCode } from '~/types/StatusCode';
12 | export { createApp, type NixleApp } from './createApp';
13 | export * from './router';
14 | export * from './createService';
15 | export * from './createGuard';
16 | export * from './createMiddleware';
17 | export * from './createModule';
18 | export * from './provider/createProvider';
19 | export * from './plugins/createPlugin';
20 | export type { Logger } from './logger';
21 | export { createError, isNixleError, type ErrorOptions } from './createError';
22 |
--------------------------------------------------------------------------------
/docs/plugins/swagger.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Swagger
6 |
7 | In nearest future, we won't implement Swagger support as separate plugin. Instead, you should use Swagger plugin depends on framework you use.
8 |
9 | ## Example
10 |
11 | For example if you use [Fastify](/providers/fastify) provider, you can use [@fastify/swagger](https://github.com/fastify/fastify-swagger) plugin:
12 |
13 | ```ts
14 | import fastify from 'fastify';
15 | import swagger from '@fastify/swagger';
16 | import { createApp } from 'nixle';
17 | import { fastifyProvider } from '@nixle/fastify';
18 |
19 | const fastifyApp = fastify();
20 |
21 | fastifyApp.register(swagger, {
22 | swagger: {
23 | // Swagger options
24 | },
25 | });
26 |
27 | const { app } = createApp({
28 | provider: fastifyProvider(fastifyApp),
29 | });
30 |
31 | app.listen({ port: 3000 });
32 | ```
33 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: home
3 |
4 | hero:
5 | name: Nixle
6 | text: Universal server-side framework
7 | tagline: Backend for everyone and everywhere.
8 | actions:
9 | - theme: brand
10 | text: Getting Started
11 | link: /introduction/getting-started
12 | - theme: alt
13 | text: Why Nixle?
14 | link: /introduction/why
15 | - theme: alt
16 | text: Examples
17 | link: https://github.com/letstri/nixle/tree/main/examples
18 |
19 | features:
20 | - title: Frameworks
21 | icon: 📋
22 | details: Use Nuxt Server, Express, Fastify, etc. with one beautiful structure
23 | - title: TypeScript
24 | icon: ⚙️
25 | details: Wonderful TypeScript support for all frameworks and our code
26 | - title: Simple
27 | icon: 🔨
28 | details: Connect to provider, create router > service and use. Just try it!
29 | ---
30 |
--------------------------------------------------------------------------------
/packages/nixle/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | namespace Nixle {
3 | interface RouterContext {
4 | }
5 | interface ServiceContext {
6 | }
7 | interface Env extends Record {
8 | }
9 | }
10 | }
11 | export type { HTTPMethod } from './types/HTTPMethod';
12 | export type { CookieOptions } from './types/CookieOptions';
13 | export { StatusCode } from './types/StatusCode';
14 | export { createApp, type NixleApp } from './createApp';
15 | export * from './router';
16 | export * from './createService';
17 | export * from './createGuard';
18 | export * from './createMiddleware';
19 | export * from './createModule';
20 | export * from './provider/createProvider';
21 | export * from './plugins/createPlugin';
22 | export type { Logger } from './logger';
23 | export { createError, isNixleError, type ErrorOptions } from './createError';
24 |
--------------------------------------------------------------------------------
/docs/public/logo.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/packages/nixle/dist/logger.d.ts:
--------------------------------------------------------------------------------
1 | import { type ConsolaOptions } from 'consola';
2 | import { type ColorName } from 'consola/utils';
3 | export type Logger = typeof log;
4 | export declare const createLogger: (options: Partial) => void;
5 | /**
6 | * @description Log message
7 | *
8 | * @param {string} message
9 | *
10 | * @example log.info('Hello world')
11 | */
12 | export declare const log: {
13 | info: (...messages: any[]) => void;
14 | success: (...messages: any[]) => void;
15 | warn: (...messages: any[]) => void;
16 | error: (...messages: any[]) => void;
17 | fatal: (...messages: any[]) => void;
18 | debug: (...messages: any[]) => void;
19 | trace: (...messages: any[]) => void;
20 | silent: (...messages: any[]) => void;
21 | log: (...messages: any[]) => void;
22 | fail: (...messages: any[]) => void;
23 | verbose: (...messages: any[]) => void;
24 | };
25 | export declare const contextLog: (context: string, color?: ColorName) => typeof log;
26 |
--------------------------------------------------------------------------------
/docs/providers/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Create a custom provider
6 |
7 | If our list of providers does not include the one you need, you can create your own custom provider. An example can be found in our repository, such as [fastify](https://github.com/letstri/nixle/blob/main/packages/fastify/src/index.ts).
8 |
9 | In short, you need to import the `createProvider` function from `nixle` and call it with the required fields.
10 |
11 | ```ts
12 | import { createProvider, type RouteHandlerContext } from 'nixle';
13 |
14 | export interface Request extends YourRequest {}
15 | export interface Response extends YourResponse {}
16 |
17 | const provider = createProvider((app) => {
18 | return {
19 | app,
20 | createRoute: ({ method, path, handler }) =>
21 | app[method](path, async (request, response) => {
22 | const formattedContext: RouteHandlerContext = {};
23 |
24 | return handler(formattedContext);
25 | }),
26 | };
27 | });
28 | ```
29 |
--------------------------------------------------------------------------------
/packages/nixle/src/createGuard.ts:
--------------------------------------------------------------------------------
1 | import { createError, StatusCode, type RouteHandlerContext } from '.';
2 | import type { NixleError } from './createError';
3 | import { contextLog, type log } from './logger';
4 |
5 | export interface GuardFunction {
6 | (context: RouteHandlerContext & { log: typeof log }): Promise | void;
7 | }
8 |
9 | export interface Guard {
10 | (context: RouteHandlerContext): Promise | void;
11 | }
12 |
13 | export function createGuard(name: Lowercase, guard: GuardFunction): Guard {
14 | return async (context) => {
15 | try {
16 | await guard({ ...context, log: contextLog(name.toLowerCase(), 'bgGreenBright') });
17 | } catch (e) {
18 | throw createError({
19 | message: (e as NixleError)?.message || `Oops, guard "${name.toLowerCase()}" was failed`,
20 | statusCode: (e as NixleError)?.statusCode || StatusCode.BAD_REQUEST,
21 | details: (e as NixleError)?.details,
22 | });
23 | }
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2023 Valerii Strilets
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/packages/nixle/src/createMiddleware.ts:
--------------------------------------------------------------------------------
1 | import { createError, StatusCode, type RouteHandlerContext } from '.';
2 | import type { NixleError } from './createError';
3 | import { contextLog, type log } from './logger';
4 |
5 | export interface MiddlewareFunction {
6 | (context: RouteHandlerContext & { log: typeof log }): Promise | void;
7 | }
8 |
9 | export interface Middleware {
10 | (context: RouteHandlerContext): Promise | void;
11 | }
12 |
13 | export function createMiddleware(
14 | name: Lowercase,
15 | middleware: MiddlewareFunction,
16 | ): Middleware {
17 | return async (context) => {
18 | try {
19 | await middleware({ ...context, log: contextLog(name.toLowerCase(), 'bgYellowBright') });
20 | } catch (e) {
21 | throw createError({
22 | message:
23 | (e as NixleError)?.message || `Oops, middleware "${name.toLowerCase()}" was failed`,
24 | statusCode: (e as NixleError)?.statusCode || StatusCode.INTERNAL_SERVER_ERROR,
25 | details: (e as NixleError)?.details,
26 | });
27 | }
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/custom.scss:
--------------------------------------------------------------------------------
1 | :root {
2 | --vp-nav-logo-height: 12px;
3 | --vp-home-hero-name-color: transparent;
4 | --vp-home-hero-name-background: linear-gradient(150deg, #315eff 10%, #fa00ff);
5 | --vp-c-brand-1: #6635fe;
6 | --vp-c-brand-2: #885aff;
7 | --vp-c-brand-3: #6635fe;
8 | --vp-c-gray-soft: rgba(101, 117, 133, 0.08);
9 | --vp-sidebar-bg-color: transparent;
10 | }
11 |
12 | @media (max-width: 960px) {
13 | :root {
14 | --vp-sidebar-bg-color: var(--vp-c-bg-alt);
15 | }
16 | }
17 |
18 | .dark {
19 | --vp-c-bg: #101010;
20 | --vp-c-bg-soft: #17181b;
21 | --vp-c-divider: #1b1b1e;
22 | }
23 |
24 | .VPHome .VPHero {
25 | @media screen and (min-width: 640px) {
26 | padding-bottom: 120px;
27 | }
28 |
29 | .main {
30 | display: flex;
31 | flex-direction: column;
32 | align-items: center;
33 | text-align: center;
34 |
35 | .text {
36 | max-width: 900px;
37 | }
38 |
39 | .actions {
40 | justify-content: center;
41 | }
42 | }
43 | }
44 |
45 | .VPNav {
46 | .VPNavBarTitle {
47 | background-color: var(--vp-c-bg);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/elysia/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | import type { Elysia, Context } from 'elysia';
2 | type ElysiaRequest = Context['request'];
3 | type ElysiaResponse = Context['set'];
4 | export interface Request extends ElysiaRequest {
5 | }
6 | export interface Response extends ElysiaResponse {
7 | }
8 | export declare const elysiaProvider: (app: Elysia<"", false, {
9 | decorator: {};
10 | store: {};
11 | derive: {};
12 | resolve: {};
13 | }, {
14 | type: {};
15 | error: {};
16 | }, {
17 | schema: {};
18 | macro: {};
19 | macroFn: {};
20 | }, {}, {
21 | derive: {};
22 | resolve: {};
23 | schema: {};
24 | }, {
25 | derive: {};
26 | resolve: {};
27 | schema: {};
28 | }>) => import("nixle").Provider>;
49 | export {};
50 |
--------------------------------------------------------------------------------
/packages/fastify/dist/index.js:
--------------------------------------------------------------------------------
1 | import n from "@fastify/cookie";
2 | import { createProvider as m } from "nixle";
3 | const h = /* @__PURE__ */ new Map([
4 | ["Strict", "strict"],
5 | ["Lax", "lax"],
6 | ["None", "none"]
7 | ]), k = m((o) => (o.register(n), {
8 | app: o,
9 | createRoute: ({ method: s, path: d, handler: c }) => o[s](d, async (t, a) => {
10 | a.send(
11 | await c({
12 | request: t.raw,
13 | response: a.raw,
14 | method: t.raw.method,
15 | params: { ...t.params || {} },
16 | query: { ...t.query || {} },
17 | body: { ...t.body || {} },
18 | redirect: async (e, r) => a.redirect(r || 302, e),
19 | setStatusCode: (e) => a.status(e),
20 | setHeader: (e, r) => a.header(e, r),
21 | getHeader: (e) => t.headers[e] ? String(t.headers[e]) : null,
22 | headers: t.headers,
23 | getCookie: (e) => t.cookies[e] || null,
24 | setCookie: (e, r, i) => a.setCookie(e, r, {
25 | ...i,
26 | sameSite: h.get(i?.sameSite || "Strict") || "strict"
27 | })
28 | })
29 | );
30 | })
31 | }));
32 | export {
33 | k as fastifyProvider
34 | };
35 |
--------------------------------------------------------------------------------
/packages/express/dist/index.js:
--------------------------------------------------------------------------------
1 | import s from "cookie-parser";
2 | import n from "body-parser";
3 | import { createProvider as y } from "nixle";
4 | const S = /* @__PURE__ */ new Map([
5 | ["Strict", "strict"],
6 | ["Lax", "lax"],
7 | ["None", "none"]
8 | ]), g = y((o) => (o.use(s()), o.use(n.json()), {
9 | app: o,
10 | createRoute: ({ method: d, path: c, handler: m }) => o[d](c, async (t, r) => {
11 | r.send(
12 | await m({
13 | request: t,
14 | response: r,
15 | method: t.method,
16 | params: t.params || {},
17 | query: t.query || {},
18 | body: t.body,
19 | redirect: async (e, a) => r.redirect(a || 302, e),
20 | setStatusCode: (e) => r.status(e),
21 | setHeader: (e, a) => r.setHeader(e, a),
22 | getHeader: (e) => t.headers[e] ? String(t.headers[e]) : null,
23 | headers: t.headers,
24 | getCookie: (e) => t.cookies[e] || null,
25 | setCookie: (e, a, i) => r.cookie(e, a, {
26 | ...i,
27 | sameSite: S.get(i?.sameSite || "Strict") || "strict"
28 | })
29 | })
30 | );
31 | })
32 | }));
33 | export {
34 | g as expressProvider
35 | };
36 |
--------------------------------------------------------------------------------
/examples/nuxt/README.md:
--------------------------------------------------------------------------------
1 | # Nuxt 3 Minimal Starter
2 |
3 | Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
4 |
5 | ## Setup
6 |
7 | Make sure to install the dependencies:
8 |
9 | ```bash
10 | # npm
11 | npm install
12 |
13 | # pnpm
14 | pnpm install
15 |
16 | # yarn
17 | yarn install
18 |
19 | # bun
20 | bun install
21 | ```
22 |
23 | ## Development Server
24 |
25 | Start the development server on `http://localhost:3000`:
26 |
27 | ```bash
28 | # npm
29 | npm run dev
30 |
31 | # pnpm
32 | pnpm run dev
33 |
34 | # yarn
35 | yarn dev
36 |
37 | # bun
38 | bun run dev
39 | ```
40 |
41 | ## Production
42 |
43 | Build the application for production:
44 |
45 | ```bash
46 | # npm
47 | npm run build
48 |
49 | # pnpm
50 | pnpm run build
51 |
52 | # yarn
53 | yarn build
54 |
55 | # bun
56 | bun run build
57 | ```
58 |
59 | Locally preview production build:
60 |
61 | ```bash
62 | # npm
63 | npm run preview
64 |
65 | # pnpm
66 | pnpm run preview
67 |
68 | # yarn
69 | yarn preview
70 |
71 | # bun
72 | bun run preview
73 | ```
74 |
75 | Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
76 |
--------------------------------------------------------------------------------
/packages/hono/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nixle/hono",
3 | "version": "0.24.0",
4 | "description": "Universal server-side framework",
5 | "scripts": {
6 | "prepublishOnly": "npm run build",
7 | "dev": "vite",
8 | "build": "vite build && tsc && tsconfig-replace-paths"
9 | },
10 | "type": "module",
11 | "files": [
12 | "dist"
13 | ],
14 | "main": "./dist/index.js",
15 | "types": "./dist/index.d.ts",
16 | "license": "MIT",
17 | "publishConfig": {
18 | "access": "public"
19 | },
20 | "homepage": "https://nixle.letstri.dev",
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/letstri/nixle.git",
24 | "directory": "packages/hono"
25 | },
26 | "bugs": {
27 | "url": "https://github.com/letstri/nixle/issues"
28 | },
29 | "author": "Valerii Strilets ",
30 | "funding": {
31 | "type": "opencollective",
32 | "url": "https://opencollective.com/nixle"
33 | },
34 | "devDependencies": {
35 | "tsconfig-replace-paths": "^0.0.14",
36 | "typescript": "^5.5.4",
37 | "vite": "^5.3.4"
38 | },
39 | "peerDependencies": {
40 | "nixle": "workspace:^",
41 | "hono": "^4"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/elysia/dist/index.js:
--------------------------------------------------------------------------------
1 | import { createProvider as o, isNixleError as m } from "nixle";
2 | const l = /* @__PURE__ */ new Map([
3 | ["Strict", "strict"],
4 | ["Lax", "lax"],
5 | ["None", "none"]
6 | ]), y = o((t) => (t.onError(({ error: a, set: i }) => (m(a) && (i.status = a.statusCode), a)), {
7 | app: t,
8 | createRoute: ({ method: a, path: i, handler: d }) => t.route(a, i, async (e) => d({
9 | request: e.request,
10 | response: e.set,
11 | method: e.request.method,
12 | params: e.params || {},
13 | query: e.query || {},
14 | body: e.body || {},
15 | redirect: async (r, s) => {
16 | s && (e.set.status = s), e.set.redirect = r;
17 | },
18 | setStatusCode: (r) => e.set.status = r,
19 | setHeader: (r, s) => e.set.headers[r] = s,
20 | getHeader: (r) => e.request.headers.get(r),
21 | headers: Object.fromEntries(e.request.headers.entries()),
22 | setCookie: (r, s, u) => {
23 | u && e.cookie[r].set({
24 | ...u,
25 | sameSite: l.get(u?.sameSite || "Strict") || "strict"
26 | }), e.cookie[r].value = s;
27 | },
28 | getCookie: (r) => e.cookie[r].value || null
29 | }))
30 | }));
31 | export {
32 | y as elysiaProvider
33 | };
34 |
--------------------------------------------------------------------------------
/packages/elysia/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nixle/elysia",
3 | "version": "0.24.0",
4 | "description": "Universal server-side framework",
5 | "scripts": {
6 | "prepublishOnly": "npm run build",
7 | "dev": "vite",
8 | "build": "vite build && tsc && tsconfig-replace-paths"
9 | },
10 | "type": "module",
11 | "files": [
12 | "dist"
13 | ],
14 | "main": "./dist/index.js",
15 | "types": "./dist/index.d.ts",
16 | "license": "MIT",
17 | "publishConfig": {
18 | "access": "public"
19 | },
20 | "homepage": "https://nixle.letstri.dev",
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/letstri/nixle.git",
24 | "directory": "packages/elysia"
25 | },
26 | "bugs": {
27 | "url": "https://github.com/letstri/nixle/issues"
28 | },
29 | "author": "Valerii Strilets ",
30 | "funding": {
31 | "type": "opencollective",
32 | "url": "https://opencollective.com/nixle"
33 | },
34 | "devDependencies": {
35 | "tsconfig-replace-paths": "^0.0.14",
36 | "typescript": "^5.5.4",
37 | "vite": "^5.3.4"
38 | },
39 | "peerDependencies": {
40 | "elysia": "^1",
41 | "nixle": "workspace:^"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/nitro/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nixle/nitro",
3 | "version": "0.24.0",
4 | "description": "Universal server-side framework",
5 | "scripts": {
6 | "prepublishOnly": "npm run build",
7 | "dev": "vite",
8 | "build": "vite build && tsc && tsconfig-replace-paths"
9 | },
10 | "type": "module",
11 | "files": [
12 | "dist"
13 | ],
14 | "main": "./dist/index.js",
15 | "types": "./dist/index.d.ts",
16 | "license": "MIT",
17 | "publishConfig": {
18 | "access": "public"
19 | },
20 | "homepage": "https://nixle.letstri.dev",
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/letstri/nixle.git",
24 | "directory": "packages/nitro"
25 | },
26 | "bugs": {
27 | "url": "https://github.com/letstri/nixle/issues"
28 | },
29 | "author": "Valerii Strilets ",
30 | "funding": {
31 | "type": "opencollective",
32 | "url": "https://opencollective.com/nixle"
33 | },
34 | "devDependencies": {
35 | "tsconfig-replace-paths": "^0.0.14",
36 | "typescript": "^5.5.4",
37 | "vite": "^5.3.4"
38 | },
39 | "peerDependencies": {
40 | "h3": "^1",
41 | "nitropack": "^2",
42 | "nixle": "workspace:^"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/zod/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nixle/zod",
3 | "version": "0.24.0",
4 | "description": "Universal server-side framework",
5 | "scripts": {
6 | "prepublishOnly": "npm run build",
7 | "dev": "vite",
8 | "build": "vite build && tsc && tsconfig-replace-paths"
9 | },
10 | "type": "module",
11 | "files": [
12 | "dist"
13 | ],
14 | "main": "./dist/index.js",
15 | "types": "./dist/index.d.ts",
16 | "license": "MIT",
17 | "publishConfig": {
18 | "access": "public"
19 | },
20 | "homepage": "https://nixle.letstri.dev",
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/letstri/nixle.git",
24 | "directory": "packages/zod"
25 | },
26 | "bugs": {
27 | "url": "https://github.com/letstri/nixle/issues"
28 | },
29 | "author": "Valerii Strilets ",
30 | "funding": {
31 | "type": "opencollective",
32 | "url": "https://opencollective.com/nixle"
33 | },
34 | "dependencies": {
35 | "zod": "^3.23.8"
36 | },
37 | "devDependencies": {
38 | "tsconfig-replace-paths": "^0.0.14",
39 | "typescript": "^5.5.4",
40 | "vite": "^5.3.4"
41 | },
42 | "peerDependencies": {
43 | "nixle": "workspace:^"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/ofetch/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nixle/ofetch",
3 | "version": "0.24.0",
4 | "description": "Universal server-side framework",
5 | "scripts": {
6 | "prepublishOnly": "npm run build",
7 | "dev": "vite",
8 | "build": "vite build && tsc && tsconfig-replace-paths"
9 | },
10 | "type": "module",
11 | "files": [
12 | "dist"
13 | ],
14 | "main": "./dist/index.js",
15 | "types": "./dist/index.d.ts",
16 | "license": "MIT",
17 | "publishConfig": {
18 | "access": "public"
19 | },
20 | "homepage": "https://nixle.letstri.dev",
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/letstri/nixle.git",
24 | "directory": "packages/ofetch"
25 | },
26 | "bugs": {
27 | "url": "https://github.com/letstri/nixle/issues"
28 | },
29 | "author": "Valerii Strilets ",
30 | "funding": {
31 | "type": "opencollective",
32 | "url": "https://opencollective.com/nixle"
33 | },
34 | "dependencies": {
35 | "ofetch": "^1.3.4"
36 | },
37 | "devDependencies": {
38 | "tsconfig-replace-paths": "^0.0.14",
39 | "typescript": "^5.5.4",
40 | "vite": "^5.3.4"
41 | },
42 | "peerDependencies": {
43 | "nixle": "workspace:^"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/docs/providers/elysia.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Elysia
6 |
7 | To use Elysia as the main provider with Nixle, you need to install the `@nixle/elysia` package. This package provides the necessary functionality for integrating Elysia into your Nixle application.
8 |
9 | ## Install
10 |
11 | You can install the `@nixle/elysia` package using npm, pnpm, yarn, or bun:
12 |
13 | ::: code-group
14 |
15 | ```sh [npm]
16 | npm i @nixle/elysia
17 | ```
18 |
19 | ```sh [pnpm]
20 | pnpm add @nixle/elysia
21 | ```
22 |
23 | ```sh [yarn]
24 | yarn add @nixle/elysia
25 | ```
26 |
27 | ```sh [bun]
28 | bun i @nixle/elysia
29 | ```
30 |
31 | :::
32 |
33 | ## Setup
34 |
35 |
36 | ```ts
37 | import { Elysia } from 'elysia';
38 | import { createApp, createRouter } from 'nixle';
39 | import { elysiaProvider } from '@nixle/elysia';
40 |
41 | const usersRouter = createRouter('/users', ({ route }) => [
42 | route.get('/', () => 'Hello Elysia!'),
43 | ]);
44 |
45 | const { app } = createApp({
46 | provider: elysiaProvider(new Elysia()),
47 | routers: [usersRouter],
48 | });
49 |
50 | app.listen(4000);
51 | ```
52 |
53 |
54 | ---
55 |
56 | [Example](https://github.com/letstri/nixle/blob/main/examples/elysia/index.ts)
57 |
--------------------------------------------------------------------------------
/docs/providers/express.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Express
6 |
7 | To use Express as the main provider with Nixle, you need to install the `@nixle/express` package. This package provides the necessary functionality for integrating Express into your Nixle application.
8 |
9 | ## Install
10 |
11 | You can install the `@nixle/express` package using npm, pnpm, yarn, or bun:
12 |
13 | ::: code-group
14 |
15 | ```sh [npm]
16 | npm i @nixle/express
17 | ```
18 |
19 | ```sh [pnpm]
20 | pnpm add @nixle/express
21 | ```
22 |
23 | ```sh [yarn]
24 | yarn add @nixle/express
25 | ```
26 |
27 | ```sh [bun]
28 | bun i @nixle/express
29 | ```
30 |
31 | :::
32 |
33 | ## Setup
34 |
35 |
36 | ```ts
37 | import express from 'express';
38 | import { createApp, createRouter } from 'nixle';
39 | import { expressProvider } from '@nixle/express';
40 |
41 | const usersRouter = createRouter('/users', ({ route }) => [
42 | route.get('/', () => 'Hello Express!'),
43 | ]);
44 |
45 | const { app } = createApp({
46 | provider: expressProvider(express()),
47 | routers: [usersRouter],
48 | });
49 |
50 | app.listen(4000);
51 | ```
52 |
53 |
54 | ---
55 |
56 | [Example](https://github.com/letstri/nixle/blob/main/examples/express/index.mjs)
57 |
--------------------------------------------------------------------------------
/packages/fastify/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nixle/fastify",
3 | "version": "0.24.0",
4 | "description": "Universal server-side framework",
5 | "scripts": {
6 | "prepublishOnly": "npm run build",
7 | "dev": "vite",
8 | "build": "vite build && tsc && tsconfig-replace-paths"
9 | },
10 | "type": "module",
11 | "files": [
12 | "dist"
13 | ],
14 | "main": "./dist/index.js",
15 | "types": "./dist/index.d.ts",
16 | "license": "MIT",
17 | "publishConfig": {
18 | "access": "public"
19 | },
20 | "homepage": "https://nixle.letstri.dev",
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/letstri/nixle.git",
24 | "directory": "packages/fastify"
25 | },
26 | "bugs": {
27 | "url": "https://github.com/letstri/nixle/issues"
28 | },
29 | "author": "Valerii Strilets ",
30 | "funding": {
31 | "type": "opencollective",
32 | "url": "https://opencollective.com/nixle"
33 | },
34 | "dependencies": {
35 | "@fastify/cookie": "^9.3.1"
36 | },
37 | "devDependencies": {
38 | "tsconfig-replace-paths": "^0.0.14",
39 | "typescript": "^5.5.4",
40 | "vite": "^5.3.4"
41 | },
42 | "peerDependencies": {
43 | "fastify": "^4",
44 | "nixle": "workspace:^"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/docs/providers/fastify.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Fastify
6 |
7 | To use Fastify as the main provider with Nixle, you need to install the `@nixle/fastify` package. This package provides the necessary functionality for integrating Fastify into your Nixle application.
8 |
9 | ## Install
10 |
11 | You can install the `@nixle/fastify` package using npm, pnpm, yarn, or bun:
12 |
13 | ::: code-group
14 |
15 | ```sh [npm]
16 | npm i @nixle/fastify
17 | ```
18 |
19 | ```sh [pnpm]
20 | pnpm add @nixle/fastify
21 | ```
22 |
23 | ```sh [yarn]
24 | yarn add @nixle/fastify
25 | ```
26 |
27 | ```sh [bun]
28 | bun i @nixle/fastify
29 | ```
30 |
31 | :::
32 |
33 | ## Setup
34 |
35 |
36 | ```ts
37 | import fastify from 'fastify';
38 | import { createApp, createRouter } from 'nixle';
39 | import { fastifyProvider } from '@nixle/fastify';
40 |
41 | const usersRouter = createRouter('/users', ({ route }) => [
42 | route.get('/', () => 'Hello Fastify!'),
43 | ]);
44 |
45 | const { app } = createApp({
46 | provider: fastifyProvider(fastify()),
47 | routers: [usersRouter],
48 | });
49 |
50 | app.listen({ port: 4000 });
51 | ```
52 |
53 |
54 | ---
55 |
56 | [Example](https://github.com/letstri/nixle/blob/main/examples/fastify/index.mjs)
57 |
--------------------------------------------------------------------------------
/docs/providers/hono.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Hono
6 |
7 | To use Hono as the main provider with Nixle, you need to install the `@nixle/hono` package. This package provides the necessary functionality for integrating Hono into your Nixle application.
8 |
9 | ## Install
10 |
11 | You can install the `@nixle/hono` package using npm, pnpm, yarn, or bun:
12 |
13 | ::: code-group
14 |
15 | ```sh [npm]
16 | npm i @nixle/hono
17 | ```
18 |
19 | ```sh [pnpm]
20 | pnpm add @nixle/hono
21 | ```
22 |
23 | ```sh [yarn]
24 | yarn add @nixle/hono
25 | ```
26 |
27 | ```sh [bun]
28 | bun i @nixle/hono
29 | ```
30 |
31 | :::
32 |
33 | ## Setup
34 |
35 |
36 | ```ts
37 | import { Hono } from 'hono';
38 | import { serve } from '@hono/node-server';
39 | import { createApp, createRouter } from 'nixle';
40 | import { honoProvider } from '@nixle/hono';
41 |
42 | const usersRouter = createRouter('/users', ({ route }) => [
43 | route.get('/', () => 'Hello Hono!'),
44 | ]);
45 |
46 | const { app } = createApp({
47 | provider: honoProvider(new Hono()),
48 | routers: [usersRouter],
49 | });
50 |
51 | serve(app, (info) => {
52 | console.log(`Listening on http://localhost:${info.port}`);
53 | });
54 | ```
55 |
56 |
57 | ---
58 |
59 | [Example](https://github.com/letstri/nixle/blob/main/examples/hono/index.ts)
60 |
--------------------------------------------------------------------------------
/packages/nitro/dist/index.js:
--------------------------------------------------------------------------------
1 | import { defineEventHandler as c, getRouterParams as u, getQuery as m, readBody as n, sendRedirect as l, setResponseStatus as g, setHeader as p, getHeader as S, getRequestHeaders as y, setCookie as H, getCookie as f } from "h3";
2 | import { createProvider as C } from "nixle";
3 | const R = /* @__PURE__ */ new Map([
4 | ["Strict", "strict"],
5 | ["Lax", "lax"],
6 | ["None", "none"]
7 | ]), b = C((s) => ({
8 | app: s,
9 | createRoute: ({ method: o, path: i, handler: d }) => s.router.use(
10 | i,
11 | c(async (e) => d({
12 | request: e.node.req,
13 | response: e.node.res,
14 | method: e.method,
15 | params: u(e),
16 | query: m(e),
17 | body: ["post", "put", "patch", "delete"].includes(o) ? await n(e) : {},
18 | redirect: async (r, t) => {
19 | await l(e, r, t);
20 | },
21 | setStatusCode: (r) => g(e, r),
22 | setHeader: (r, t) => p(e, r, t),
23 | getHeader: (r) => S(e, r) || null,
24 | headers: Object.fromEntries(
25 | Object.entries(y(e)).filter(([, r]) => r)
26 | ),
27 | setCookie: (r, t, a) => H(e, r, t, {
28 | ...a,
29 | sameSite: R.get(a?.sameSite || "Strict") || "strict"
30 | }),
31 | getCookie: (r) => f(e, r) || null
32 | })),
33 | o
34 | )
35 | }));
36 | export {
37 | b as nitroProvider
38 | };
39 |
--------------------------------------------------------------------------------
/docs/providers/nuxt.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | title: Nuxt as backend framework
4 | ---
5 |
6 | # Nuxt
7 |
8 | Nuxt uses [Nitro](https://nitro.unjs.io/) as the backend framework for building server-side applications.
9 |
10 | To use Nitro as the main provider with Nixle, you need to install the `@nixle/nitro` package. This package provides the necessary functionality for integrating Nitro into your Nixle application.
11 |
12 | ## Install
13 |
14 | You can install the `@nixle/nitro` package using npm, pnpm, yarn, or bun:
15 |
16 | ::: code-group
17 |
18 | ```sh [npm]
19 | npm i @nixle/nitro
20 | ```
21 |
22 | ```sh [pnpm]
23 | pnpm add @nixle/nitro
24 | ```
25 |
26 | ```sh [yarn]
27 | yarn add @nixle/nitro
28 | ```
29 |
30 | ```sh [bun]
31 | bun i @nixle/nitro
32 | ```
33 |
34 | :::
35 |
36 | ## Setup
37 |
38 |
39 | ```ts
40 | // server/plugins/nixle.ts
41 | import { createApp, createRouter } from 'nixle';
42 | import { nitroProvider } from '@nixle/nitro';
43 |
44 | const usersRouter = createRouter('/users', ({ route }) => [
45 | route.get('/', () => 'Hello Nuxt!'),
46 | ]);
47 |
48 | export default defineNitroPlugin((nitroApp) => {
49 | createApp({
50 | provider: nitroProvider(nitroApp),
51 | routers: [usersRouter],
52 | });
53 | });
54 | ```
55 |
56 |
57 | ---
58 |
59 | [Example](https://github.com/letstri/nixle/tree/main/examples/nuxt)
60 |
--------------------------------------------------------------------------------
/packages/express/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nixle/express",
3 | "version": "0.24.0",
4 | "description": "Universal server-side framework",
5 | "scripts": {
6 | "prepublishOnly": "npm run build",
7 | "dev": "vite",
8 | "build": "vite build && tsc && tsconfig-replace-paths"
9 | },
10 | "type": "module",
11 | "files": [
12 | "dist"
13 | ],
14 | "main": "./dist/index.js",
15 | "types": "./dist/index.d.ts",
16 | "license": "MIT",
17 | "publishConfig": {
18 | "access": "public"
19 | },
20 | "homepage": "https://nixle.letstri.dev",
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/letstri/nixle.git",
24 | "directory": "packages/express"
25 | },
26 | "bugs": {
27 | "url": "https://github.com/letstri/nixle/issues"
28 | },
29 | "author": "Valerii Strilets ",
30 | "funding": {
31 | "type": "opencollective",
32 | "url": "https://opencollective.com/nixle"
33 | },
34 | "dependencies": {
35 | "body-parser": "^1.20.2",
36 | "cookie-parser": "^1.4.6"
37 | },
38 | "devDependencies": {
39 | "@types/body-parser": "^1.19.5",
40 | "@types/cookie-parser": "^1.4.6",
41 | "tsconfig-replace-paths": "^0.0.14",
42 | "typescript": "^5.5.4",
43 | "vite": "^5.3.4"
44 | },
45 | "peerDependencies": {
46 | "@types/express": "^4",
47 | "express": "^4",
48 | "nixle": "workspace:^"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/nixle/src/createService.ts:
--------------------------------------------------------------------------------
1 | import { StatusCode, createError, type RouteHandlerContext } from '.';
2 | import { contextLog, type log } from './logger';
3 | import { getEnv } from '~/env';
4 |
5 | let serviceContext: Nixle.ServiceContext = {};
6 |
7 | export const extendServiceContext = (options: T) => {
8 | Object.assign(serviceContext, options);
9 | };
10 |
11 | interface ServiceContext extends Nixle.ServiceContext {
12 | log: typeof log;
13 | env: RouteHandlerContext['env'];
14 | }
15 |
16 | interface ServiceFunction {
17 | (context: ServiceContext): M;
18 | }
19 |
20 | export interface Service<
21 | M extends Record any> = Record any>,
22 | > {
23 | (): M;
24 | }
25 |
26 | export function createService<
27 | N extends string,
28 | M extends Record any> = Record any>,
29 | >(name: Lowercase, methods: ServiceFunction): Service {
30 | return () => {
31 | try {
32 | return methods({
33 | log: contextLog(name.toLowerCase(), 'bgCyan'),
34 | env: getEnv(),
35 | ...serviceContext,
36 | });
37 | } catch (e) {
38 | throw createError({
39 | message: `Oops, service "${name.toLowerCase()}" was failed`,
40 | statusCode: StatusCode.INTERNAL_SERVER_ERROR,
41 | details: e,
42 | });
43 | }
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/packages/nixle/dist/createError.d.ts:
--------------------------------------------------------------------------------
1 | import { log } from './logger';
2 | import { StatusCode } from '.';
3 | export interface ErrorOptions {
4 | /**
5 | * @example 'User with id 1 not found'
6 | */
7 | message: string;
8 | /**
9 | * @default 400 // Bad Request
10 | *
11 | * @example StatusCode.BAD_REQUEST
12 | */
13 | statusCode?: number;
14 | /**
15 | * @example user_not_found
16 | */
17 | code?: string | number;
18 | /**
19 | * Should be an object
20 | *
21 | * @example { id: 1 }
22 | */
23 | details?: D;
24 | }
25 | export declare class NixleError extends Error {
26 | constructor({ statusCode, message, details, code }: ErrorOptions);
27 | time: string;
28 | statusCode: StatusCode;
29 | message: string;
30 | details?: D;
31 | code?: string | number;
32 | }
33 | export declare function createError(options: ErrorOptions): NixleError;
34 | export declare function createError(message: string, statusCode?: StatusCode): NixleError;
35 | export declare const isNixleError: (error: any) => error is NixleError;
36 | export declare const logError: (error: any, _log: typeof log) => Promise;
37 | export declare const transformErrorToResponse: (error: any, statusCode: StatusCode) => Omit, keyof NixleError>, "name">;
38 |
--------------------------------------------------------------------------------
/docs/plugins/ofetch.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # ofetch
6 |
7 | [ofetch](https://github.com/unjs/ofetch) is a HTTP client for Node.js and browsers. We created a plugin for easily connect ofetch to your services and sending HTTP requests for your needs.
8 |
9 | ## Install
10 |
11 | You can install the `@nixle/ofetch` package using npm, pnpm, yarn, or bun:
12 |
13 | ::: code-group
14 |
15 | ```sh [npm]
16 | npm i @nixle/ofetch
17 | ```
18 |
19 | ```sh [pnpm]
20 | pnpm add @nixle/ofetch
21 | ```
22 |
23 | ```sh [yarn]
24 | yarn add @nixle/ofetch
25 | ```
26 |
27 | ```sh [bun]
28 | bun i @nixle/ofetch
29 | ```
30 |
31 | :::
32 |
33 | ## Setup
34 |
35 | To use ofetch in your services, you need to add the `ofetchPlugin` to the `plugins` array when creating the app.
36 |
37 | ```ts
38 | import { createApp } from 'nixle';
39 | import { ofetchPlugin } from '@nixle/ofetch';
40 |
41 | const app = createApp({
42 | plugins: [
43 | ofetchPlugin({
44 | // Any ofetch options such as base url, headers, etc.
45 | }),
46 | ],
47 | });
48 | ```
49 |
50 | ## Usage
51 |
52 | To use ofetch in your services, you can use the `ofetch` function that is available in the service context.
53 |
54 | ```ts
55 | import { createService } from 'nixle';
56 |
57 | const usersService = createService('users', ({ ofetch }) => {
58 | const getUsers = async () => {
59 | const data = await ofetch('/users');
60 |
61 | return data;
62 | };
63 |
64 | return {
65 | getUsers,
66 | };
67 | });
68 | ```
69 |
--------------------------------------------------------------------------------
/docs/overview/app.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # App
6 |
7 | App is the entry point of your application. It is a wrapper around the provider and provides a way to register routers.
8 |
9 | ## Usage
10 |
11 | Create an app with the `createApp` function. This function takes a config object with the following properties:
12 |
13 | - `globalPrefix` - A prefix that will be used for all routes.
14 | - `provider` - A provider that will be used to create a store.
15 | - `routers` - An array with routers.
16 | - `logger` - A [consola](/overview/logger) config that will be used to update options of the logger.
17 | - `env` - A [dotenv](/overview/env) config that will be used to load environment variables.
18 | - `plugins` - An array of plugins that will be used to extend the app.
19 |
20 | ```ts
21 | import { createApp } from 'nixle';
22 | import { fastifyProvider } from '@nixle/fastify';
23 | import { usersRouter } from './usersRouter';
24 |
25 | export const app = createApp({
26 | globalPrefix: '/api',
27 | provider: fastifyProvider(),
28 | routers: [usersRouter],
29 | logger: {
30 | level: 3,
31 | },
32 | env: {
33 | path: '.env',
34 | },
35 | });
36 | ```
37 |
38 | ## Returns
39 |
40 | The `createApp` function returns an object with the following properties:
41 |
42 | - `app` - An instance of the app.
43 | - `events` - An object with the following properties:
44 | - `on` - A function that takes an event name and a handler function.
45 | - `emit` - A function that takes an event name and an event data.
46 |
--------------------------------------------------------------------------------
/packages/nixle/src/utils/helpers.ts:
--------------------------------------------------------------------------------
1 | import { joinURL } from 'ufo';
2 |
3 | export const pick = , K extends keyof O>(
4 | obj: O,
5 | fields: K[],
6 | ): Pick =>
7 | Object.fromEntries(Object.entries(obj).filter(([key]) => fields.includes(key as K))) as Pick<
8 | O,
9 | K
10 | >;
11 |
12 | export const exclude = , K extends keyof O>(
13 | obj: O,
14 | fields: K[],
15 | ): Exclude =>
16 | Object.fromEntries(Object.entries(obj).filter(([key]) => !fields.includes(key as K))) as Exclude<
17 | O,
18 | K
19 | >;
20 |
21 | export const isPrimitive = (val: any) => val !== Object(val);
22 |
23 | export const joinPath = (...paths: string[]) => {
24 | const path = joinURL('', ...paths);
25 | const _path = path.startsWith('/') ? path : `/${path}`;
26 |
27 | return _path.endsWith('/') ? _path.slice(0, -1) : _path;
28 | };
29 |
30 | export const tryToParse = (value: string) => {
31 | try {
32 | const parsed = JSON.parse(value);
33 |
34 | return typeof parsed === 'number' ||
35 | typeof parsed === 'boolean' ||
36 | parsed === undefined ||
37 | parsed === null
38 | ? parsed
39 | : value;
40 | } catch (err) {
41 | return value;
42 | }
43 | };
44 |
45 | export const parseObject = (obj: Record) =>
46 | Object.fromEntries(
47 | Object.entries(obj).map(([key, value]) => [
48 | key,
49 | Array.isArray(value) ? value.map(tryToParse) : tryToParse(value),
50 | ]),
51 | ) as Record;
52 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nixle/app",
3 | "version": "0.24.0",
4 | "scripts": {
5 | "preinstall": "npx only-allow pnpm",
6 | "build": "pnpm -r --filter='./packages/nixle' run build && pnpm -r --filter='./packages/*' --filter='!./packages/nixle' run build",
7 | "dev": "pnpm -r --parallel --filter='./packages/*' run dev",
8 | "publish": "pnpm publish -r --filter='./packages/*'",
9 | "prepare": "husky install",
10 | "patch-versions": "tsx scripts/patch-versions.ts"
11 | },
12 | "keywords": [
13 | "nixle",
14 | "framework",
15 | "server",
16 | "universal",
17 | "typescript",
18 | "node",
19 | "fastify",
20 | "express",
21 | "bun",
22 | "elysia",
23 | "hono",
24 | "server-side"
25 | ],
26 | "engines": {
27 | "node": "^18.0.0 || >=20.0.0"
28 | },
29 | "license": "MIT",
30 | "homepage": "https://nixle.letstri.dev",
31 | "repository": {
32 | "type": "git",
33 | "url": "git+https://github.com/letstri/nixle.git"
34 | },
35 | "author": {
36 | "name": "Valerii Strilets",
37 | "email": "valerii.strilets@gmail.com"
38 | },
39 | "funding": {
40 | "type": "opencollective",
41 | "url": "https://opencollective.com/nixle"
42 | },
43 | "workspaces": [
44 | "packages/*"
45 | ],
46 | "packageManager": "pnpm@9.7.1",
47 | "devDependencies": {
48 | "@commitlint/cli": "^19",
49 | "@commitlint/config-conventional": "^19",
50 | "@types/fs-extra": "^11",
51 | "fs-extra": "^11",
52 | "husky": "^9",
53 | "tsx": "^4",
54 | "typescript": "^5",
55 | "vite": "^5"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/zod/dist/index.js:
--------------------------------------------------------------------------------
1 | import { createPlugin as y, createError as p, StatusCode as d } from "nixle";
2 | import { z as s } from "zod";
3 | const u = (t, o) => {
4 | const a = (r, { partial: n }) => {
5 | if (typeof t == "function") {
6 | const e = t(s);
7 | return e instanceof s.ZodObject ? n ? e.partial().parseAsync(r) : e.parseAsync(r) : e instanceof s.ZodEffects ? (n && console.warn("Partial validation is not supported with ZodEffects"), e.parseAsync(r)) : n ? s.object(e).partial().parseAsync(r) : s.object(e).parseAsync(r);
8 | }
9 | return t instanceof s.ZodObject ? n ? t.partial().parseAsync(r) : t.parseAsync(r) : n ? s.object(t).partial().parseAsync(r) : s.object(t).parseAsync(r);
10 | }, i = async (r) => {
11 | try {
12 | return await r();
13 | } catch (n) {
14 | const e = n, f = e.errors.filter(({ path: c }) => c).reduce(
15 | (c, l) => ({
16 | ...c,
17 | [l.path.join(".")]: l.message
18 | }),
19 | {}
20 | );
21 | throw p({
22 | message: o?.message || "Validation error",
23 | statusCode: o?.statusCode || d.BAD_REQUEST,
24 | details: f ? { paths: f } : { errors: e.errors }
25 | });
26 | }
27 | };
28 | return {
29 | validate: async (r) => i(() => a(r, { partial: !1 })),
30 | validatePartial: async (r) => i(() => a(r, { partial: !0 })),
31 | $infer: {},
32 | $inferPartial: {}
33 | };
34 | }, P = () => y("zod", ({ extendServiceContext: t, extendRouterContext: o }) => {
35 | o({ zodObject: u }), t({ zodObject: u });
36 | });
37 | export {
38 | u as zodObject,
39 | P as zodPlugin
40 | };
41 |
--------------------------------------------------------------------------------
/packages/nixle/dist/createApp.d.ts:
--------------------------------------------------------------------------------
1 | import type { ConsolaOptions } from 'consola';
2 | import type dotenv from 'dotenv';
3 | import type { Provider } from './provider/createProvider';
4 | import type { Plugin } from './plugins/createPlugin';
5 | import { type Router } from '.';
6 | import { type Middleware } from './createMiddleware';
7 | import type { Module } from './createModule';
8 | type ConvertModules = M[number]['routers'];
9 | type ConvertRouters = {
10 | [P in R[number]['path']]: Extract['$inferRoutes'];
13 | };
14 | export interface AppOptions {
15 | provider: Provider;
16 | routers?: Routers;
17 | modules?: Modules;
18 | plugins?: Plugin[];
19 | middlewares?: Middleware[];
20 | logger?: Partial | false;
21 | env?: dotenv.DotenvConfigOptions;
22 | globalPrefix?: string;
23 | }
24 | export type NixleApp = ReturnType;
25 | export declare function createApp(options: AppOptions): {
26 | app: any;
27 | hooks: Pick>, "afterEach" | "beforeEach" | "callHook" | "hook" | "hookOnce">;
36 | $inferRouters: ConvertRouters & ConvertRouters>;
37 | };
38 | export {};
39 |
--------------------------------------------------------------------------------
/docs/introduction/why.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Why Nixle?
6 |
7 | ## Headache
8 |
9 | When starting a new project, it's important to have a clear understanding of how to design a proper architecture and where to place logic and routes. Unfortunately, many frameworks don't make it easy to create well-structured applications with minimal code. This can lead to frustration and inefficiencies during development.
10 |
11 | Looking ahead to the future, it's also crucial to integrate with SSR frameworks like Nuxt and Next. Both frameworks already have a server built-in, but it can be challenging to integrate them with existing frameworks.
12 |
13 | ## Solution
14 |
15 | That's where **Nixle** comes in. It's designed to solve these problems by providing a simple and intuitive way to create well-structured applications with minimal code. Nixle builds on top of popular frameworks like Fastify, Express, Elysia, Hono, and Nuxt, giving developers a consistent and familiar experience. So you can use the same plugins, middlewares, and other features that you're already know for any of these frameworks.
16 |
17 | One of the great things about Nixle is that it's built with **TypeScript**, which brings many benefits like type safety and code completion. This makes it easier to write code and reduces the number of bugs in your application.
18 |
19 | ## SSR Compatibility
20 |
21 | Future-proof your projects by integrating with SSR frameworks like [Nuxt](https://nuxt.com). Say goodbye to separate frontend and backend applications and instead build a single application that can be deployed anywhere. With Nixle, you can seamlessly incorporate SSR capabilities, opening up new possibilities for improved performance and user experience.
22 |
--------------------------------------------------------------------------------
/packages/nixle/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nixle",
3 | "version": "0.24.0",
4 | "description": "Universal Server-Side Framework. Backend for everyone and everywhere.",
5 | "scripts": {
6 | "prepublishOnly": "npm run build",
7 | "dev": "vite",
8 | "build": "vite build && tsc && tsconfig-replace-paths"
9 | },
10 | "type": "module",
11 | "files": [
12 | "dist"
13 | ],
14 | "main": "./dist/index.js",
15 | "types": "./dist/index.d.ts",
16 | "keywords": [
17 | "backend",
18 | "nixle",
19 | "framework",
20 | "server",
21 | "universal",
22 | "typescript",
23 | "node",
24 | "fastify",
25 | "express",
26 | "bun",
27 | "elysia",
28 | "hono",
29 | "server-side"
30 | ],
31 | "engines": {
32 | "node": "^18.0.0 || >=20.0.0"
33 | },
34 | "license": "MIT",
35 | "publishConfig": {
36 | "access": "public"
37 | },
38 | "homepage": "https://nixle.letstri.dev",
39 | "repository": {
40 | "type": "git",
41 | "url": "git+https://github.com/letstri/nixle.git",
42 | "directory": "packages/nixle"
43 | },
44 | "bugs": {
45 | "url": "https://github.com/letstri/nixle/issues"
46 | },
47 | "author": "Valerii Strilets ",
48 | "funding": {
49 | "type": "opencollective",
50 | "url": "https://opencollective.com/nixle"
51 | },
52 | "dependencies": {
53 | "consola": "^3.2.3",
54 | "dayjs": "^1.11.12",
55 | "dotenv": "^16.4.5",
56 | "hookable": "^5.5.3",
57 | "ufo": "^1.5.4"
58 | },
59 | "devDependencies": {
60 | "@types/cookie": "^0.6.0",
61 | "@types/node": "^20.14.11",
62 | "tsconfig-replace-paths": "^0.0.14",
63 | "typescript": "^5.5.4",
64 | "vite": "^5.3.4"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/packages/nixle/dist/router/createRouter.d.ts:
--------------------------------------------------------------------------------
1 | import { log } from '../logger';
2 | import { type Route, route } from './createRoute';
3 | import { type RouteHandlerContext } from '..';
4 | import type { Guard } from '../createGuard';
5 | import type { ValidPath } from '../utils/types';
6 | import type { Middleware } from '../createMiddleware';
7 | declare function extendRouterContext(context: T): void;
8 | export interface RouterContext extends Nixle.RouterContext {
9 | route: typeof route;
10 | log: typeof log;
11 | env: RouteHandlerContext['env'];
12 | }
13 | interface RouterRoutesHandler {
14 | (context: RouterContext): Routes;
15 | }
16 | interface RouterOptions {
17 | middlewares?: Middleware[];
18 | guards?: Guard[];
19 | routes: RouterRoutesHandler;
20 | }
21 | export type ConvertRoutes = {
22 | [P in T[number]['path']]: {
23 | [M in Extract as M['method']]: Omit['$infer'], 'path' | 'method'>;
29 | };
30 | };
31 | export interface Router {
32 | path: Path;
33 | middlewares: Middleware[];
34 | guards: Guard[];
35 | routes: () => Routes;
36 | $inferRoutes: Routes extends Route[] ? ConvertRoutes : never;
37 | }
38 | declare function createRouter(path: ValidPath, options: RouterOptions): Router;
39 | declare function createRouter(path: ValidPath, routes: RouterRoutesHandler): Router;
40 | export { createRouter, extendRouterContext };
41 |
--------------------------------------------------------------------------------
/packages/express/src/index.ts:
--------------------------------------------------------------------------------
1 | import type { Express, Request as ExpressRequest, Response as ExpressResponse } from 'express';
2 | import cookieParser from 'cookie-parser';
3 | import bodyParser from 'body-parser';
4 | import { createProvider, type HTTPMethod } from 'nixle';
5 |
6 | export interface Request extends ExpressRequest {}
7 | export interface Response extends ExpressResponse {}
8 |
9 | const sameSiteMap = new Map([
10 | ['Strict', 'strict' as const],
11 | ['Lax', 'lax' as const],
12 | ['None', 'none' as const],
13 | ]);
14 |
15 | export const expressProvider = createProvider((app) => {
16 | app.use(cookieParser());
17 | app.use(bodyParser.json());
18 |
19 | return {
20 | app,
21 | createRoute: ({ method, path, handler }) =>
22 | app[method](path, async (request, response) => {
23 | response.send(
24 | await handler({
25 | request,
26 | response,
27 | method: request.method as HTTPMethod,
28 | params: (request.params as Record) || {},
29 | query: (request.query as Record) || {},
30 | body: request.body,
31 | redirect: async (url, status) => response.redirect(status || 302, url),
32 | setStatusCode: (code) => response.status(code),
33 | setHeader: (name, value) => response.setHeader(name, value),
34 | getHeader: (name) => (request.headers[name] ? String(request.headers[name]) : null),
35 | headers: request.headers as Record,
36 | getCookie: (key) => request.cookies[key] || null,
37 | setCookie: (key, value, options) =>
38 | response.cookie(key, value, {
39 | ...options,
40 | sameSite: sameSiteMap.get(options?.sameSite || 'Strict') || 'strict',
41 | }),
42 | }),
43 | );
44 | }),
45 | };
46 | });
47 |
--------------------------------------------------------------------------------
/docs/providers/what.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | next:
4 | text: 'Nuxt'
5 | link: '/providers/nuxt'
6 | ---
7 |
8 | # Providers
9 |
10 | When making HTTP requests, we leverage existing frameworks to avoid common mistakes and take advantage of their main functionality, such as `path` and `query` parameters.
11 |
12 | To prevent repetitive code duplication across different frameworks, we have developed a set of providers. These providers help minimize the amount of copy-pasted code and promote code reusability.
13 |
14 | ## Switch
15 |
16 | You can easily switch between providers by setting the `provider` option in the `createApp` function.
17 |
18 | ```ts
19 | import { createApp } from 'nixle';
20 | import { yourProvider } from '@nixle/provider-name';
21 |
22 | createApp({
23 | provider: yourProvider(),
24 | });
25 | ```
26 |
27 | ## Accessing the Framework
28 |
29 | Each provider returns an instance of the framework it is built for. This means that you can continue using the framework as usual. For example, if you are using Express, you can add plugins, middlewares, or routes just like you normally would.
30 |
31 | ::: tip
32 | Use it only when Nixle does not provide the functionality you need.
33 | :::
34 |
35 | ```ts
36 | import express from 'express';
37 | import cors from 'cors';
38 | import { createApp } from 'nixle';
39 | import { expressProvider } from '@nixle/express';
40 |
41 | const expressApp = express();
42 |
43 | expressApp.use(cors());
44 |
45 | const { app } = createApp({
46 | provider: expressProvider(expressApp),
47 | });
48 |
49 | // Not recommended
50 | app.get('/users', (req, res) => {
51 | res.json({ users: [] });
52 | });
53 |
54 | app.listen(3000, () => {
55 | console.log('Server is running on port 3000');
56 | });
57 | ```
58 |
59 | ## Available Providers
60 |
61 | - [Nuxt](/providers/nuxt)
62 | - [Express](/providers/express)
63 | - [Fastify](/providers/fastify)
64 | - [Elysia (Bun)](/providers/elysia)
65 | - [Hono](/providers/hono)
66 |
--------------------------------------------------------------------------------
/packages/fastify/src/index.ts:
--------------------------------------------------------------------------------
1 | import type { IncomingMessage, ServerResponse } from 'http';
2 | import type { FastifyInstance } from 'fastify';
3 | import cookie from '@fastify/cookie';
4 | import { createProvider, type HTTPMethod } from 'nixle';
5 |
6 | export interface Request extends IncomingMessage {}
7 | export interface Response extends ServerResponse {}
8 |
9 | const sameSiteMap = new Map([
10 | ['Strict', 'strict' as const],
11 | ['Lax', 'lax' as const],
12 | ['None', 'none' as const],
13 | ]);
14 |
15 | export const fastifyProvider = createProvider((app) => {
16 | app.register(cookie);
17 |
18 | return {
19 | app,
20 | createRoute: ({ method, path, handler }) =>
21 | app[method](path, async (request, reply) => {
22 | reply.send(
23 | await handler({
24 | request: request.raw,
25 | response: reply.raw,
26 | method: request.raw.method as HTTPMethod,
27 | params: ({ ...(request.params || {}) } satisfies Record) || {},
28 | query: ({ ...(request.query || {}) } satisfies Record) || {},
29 | body: ({ ...(request.body || {}) } satisfies Record) || {},
30 | redirect: async (url, status) => reply.redirect(status || 302, url),
31 | setStatusCode: (code) => reply.status(code),
32 | setHeader: (key, value) => reply.header(key, value),
33 | getHeader: (name) => (request.headers[name] ? String(request.headers[name]) : null),
34 | headers: request.headers as Record,
35 | getCookie: (key) => request.cookies[key] || null,
36 | setCookie: (key, value, options) =>
37 | reply.setCookie(key, value, {
38 | ...options,
39 | sameSite: sameSiteMap.get(options?.sameSite || 'Strict') || 'strict',
40 | }),
41 | }),
42 | );
43 | }),
44 | };
45 | });
46 |
--------------------------------------------------------------------------------
/packages/hono/src/index.ts:
--------------------------------------------------------------------------------
1 | import type { Hono, Context } from 'hono';
2 | import { setCookie, getCookie } from 'hono/cookie';
3 | import type { StatusCode } from 'hono/utils/http-status';
4 | import { createProvider, type HTTPMethod } from 'nixle';
5 |
6 | type HonoRequest = Context['req'];
7 | type HonoResponse = Context['res'];
8 |
9 | export interface Request extends HonoRequest {}
10 | export interface Response extends HonoResponse {}
11 |
12 | export const honoProvider = createProvider((app) => {
13 | return {
14 | app,
15 | createRoute: ({ method, path, handler }) => {
16 | const methods: Record = {
17 | get: app.get,
18 | post: app.post,
19 | put: app.put,
20 | patch: app.patch,
21 | delete: app.delete,
22 | options: app.options,
23 | };
24 |
25 | methods[method](path, async (context) => {
26 | return context.json(
27 | await handler({
28 | request: context.req,
29 | response: context.res,
30 | method: context.req.method as HTTPMethod,
31 | params: (context.req.param() as Record) || {},
32 | query: (context.req.query() as Record) || {},
33 | body: ['post', 'put', 'patch', 'delete'].includes(method)
34 | ? await context.req.json()
35 | : {},
36 | redirect: async (url, status) => {
37 | await context.redirect(url, status);
38 | },
39 | setStatusCode: (code) => context.status(code as StatusCode),
40 | setHeader: (key, value) => context.header(key, value),
41 | getHeader: (key) => context.req.header(key) || null,
42 | headers: context.req.header(),
43 | setCookie: (name, value, options) => setCookie(context, name, value, options),
44 | getCookie: (name) => getCookie(context, name) || null,
45 | }),
46 | );
47 | });
48 | },
49 | };
50 | });
51 |
--------------------------------------------------------------------------------
/docs/overview/logger.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Logger
6 |
7 | We integrated with [consola](https://github.com/unjs/consola) for better logging experience.
8 |
9 | ## Usage
10 |
11 | You can you log anything inside routers and services everywhere in your application with different types.
12 |
13 | Available types: `silent`, `fatal`, `error`, `warn`, `log`, `info`, `success`, `fail`, `ready`, `start`, `box`, `debug`, `trace`, `verbose`.
14 |
15 | ### Routers
16 |
17 | Each router provides `log` function in context.
18 |
19 | ```ts{5}
20 | import { createRouter, } from 'nixle';
21 |
22 | const usersRouter = createRouter('/users', ({ route, log }) => [
23 | route.get('/', () => {
24 | log.debug('Some log for debug');
25 |
26 | return [];
27 | }),
28 | ]);
29 | ```
30 |
31 | ### Services
32 |
33 | Each service provides `log` function in context.
34 |
35 | ```ts{5}
36 | import { createService } from 'nixle';
37 |
38 | const usersService = createService('users', ({ log }) => {
39 | const getUsers = () => {
40 | log.debug('Some log for debug'});
41 |
42 | return [];
43 | };
44 |
45 | return { getUsers };
46 | });
47 | ```
48 |
49 | ## Configuration
50 |
51 | For more information about configuration, please visit [consola](https://github.com/unjs/consola) repository.
52 |
53 | ```ts
54 | interface ConsolaOptions {
55 | reporters: ConsolaReporter[];
56 | types: Record;
57 | level: LogLevel;
58 | defaults: InputLogObject;
59 | throttle: number;
60 | throttleMin: number;
61 | stdout?: NodeJS.WriteStream;
62 | stderr?: NodeJS.WriteStream;
63 | mockFn?: (type: LogType, defaults: InputLogObject) => (...args: any) => void;
64 | prompt?: typeof prompt | undefined;
65 | formatOptions: FormatOptions;
66 | }
67 | ```
68 |
69 | ### Setup
70 |
71 | ```ts
72 | import { createApp } from 'nixle';
73 | import { someProvider } from '@nixle/some-provider';
74 |
75 | const { app } = createApp({
76 | provider: someProvider(),
77 | logger: {
78 | // Any consola options
79 | },
80 | router: [someRouter],
81 | });
82 | ```
83 |
--------------------------------------------------------------------------------
/packages/elysia/src/index.ts:
--------------------------------------------------------------------------------
1 | import type { Elysia, Context } from 'elysia';
2 | import { createProvider, type HTTPMethod, isNixleError } from 'nixle';
3 |
4 | type ElysiaRequest = Context['request'];
5 | type ElysiaResponse = Context['set'];
6 |
7 | export interface Request extends ElysiaRequest {}
8 | export interface Response extends ElysiaResponse {}
9 |
10 | const sameSiteMap = new Map([
11 | ['Strict', 'strict' as const],
12 | ['Lax', 'lax' as const],
13 | ['None', 'none' as const],
14 | ]);
15 |
16 | export const elysiaProvider = createProvider((app) => {
17 | app.onError(({ error, set }) => {
18 | if (isNixleError(error)) {
19 | set.status = error.statusCode;
20 | }
21 |
22 | return error;
23 | });
24 |
25 | return {
26 | app,
27 | createRoute: ({ method, path, handler }) =>
28 | app.route(method, path, async (context) => {
29 | return handler({
30 | request: context.request,
31 | response: context.set,
32 | method: context.request.method as HTTPMethod,
33 | params: context.params || {},
34 | query: (context.query as Record) || {},
35 | body: (context.body as Record) || {},
36 | redirect: async (url, status) => {
37 | if (status) {
38 | context.set.status = status;
39 | }
40 | context.set.redirect = url;
41 | },
42 | setStatusCode: (code) => (context.set.status = code),
43 | setHeader: (key, value) => (context.set.headers[key] = value),
44 | getHeader: (key) => context.request.headers.get(key),
45 | headers: Object.fromEntries(context.request.headers.entries()),
46 | setCookie: (name, value, options) => {
47 | if (options) {
48 | context.cookie[name].set({
49 | ...options,
50 | sameSite: sameSiteMap.get(options?.sameSite || 'Strict') || 'strict',
51 | });
52 | }
53 | context.cookie[name].value = value;
54 | },
55 | getCookie: (name) => context.cookie[name].value || null,
56 | });
57 | }),
58 | };
59 | });
60 |
--------------------------------------------------------------------------------
/docs/overview/errors.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Errors
6 |
7 | Nixle provides a simple way to throw exceptions.
8 |
9 | ## Overview
10 |
11 | To throw an exception in Nixle, you can use the `createError` function. This function accepts a string as the message and an optional second parameter as the status code or an object with the following properties:
12 |
13 | - `message` - A string with additional information. Example: `You are unauthorized!`.
14 | - `statusCode` - An HTTP status code. Example: `401`. You can use the `StatusCode` enum from `nixle`.
15 | - `code` - Custom error code. Example: `USER_UNAUTHORIZED_ERROR`.
16 | - `details` - An object with additional details. Example: `{ userId: 1 }`.
17 |
18 | ::: tip
19 | To set the status code, you can use the `StatusCode` enum.
20 | :::
21 |
22 | ```ts
23 | import { createError, StatusCode } from 'nixle';
24 |
25 | export const usersService = createService(() => {
26 | const getUsers = async () => {
27 | if (Math.random() > 0.5) {
28 | if (Math.random() > 0.5) {
29 | throw createError('You are unauthorized!');
30 | } else {
31 | throw createError('You are unauthorized!', StatusCode.FORBIDDEN);
32 | }
33 | } else {
34 | throw createError({
35 | message: 'You are unauthorized!',
36 | statusCode: StatusCode.UNAUTHORIZED,
37 | code: 'USER_UNAUTHORIZED_ERROR',
38 | details: { userId: 1 },
39 | });
40 | }
41 |
42 | // This code will never be executed.
43 | return ['John', 'Jane'];
44 | };
45 |
46 | return { getUsers };
47 | });
48 | ```
49 |
50 | ## Check error
51 |
52 | To check an error, you can use the `isNixleError` function. This function takes an error and returns a boolean.
53 |
54 | ```ts
55 | import { createService, createError, isNixleError } from 'nixle';
56 |
57 | const usersService = createService('users', () => {
58 | const getUsers = async () => {
59 | try {
60 | throw createError('You are unauthorized!');
61 | } catch (error) {
62 | if (isNixleError(error)) {
63 | // Handle Nixle error.
64 | } else {
65 | // Handle another error.
66 | }
67 | }
68 | };
69 |
70 | return { getUsers };
71 | });
72 | ```
73 |
--------------------------------------------------------------------------------
/packages/nitro/src/index.ts:
--------------------------------------------------------------------------------
1 | import type { IncomingMessage, ServerResponse } from 'http';
2 | import {
3 | setCookie,
4 | getCookie,
5 | getQuery,
6 | defineEventHandler,
7 | getRequestHeaders,
8 | getRouterParams,
9 | setResponseStatus,
10 | getHeader,
11 | setHeader,
12 | readBody,
13 | sendRedirect,
14 | } from 'h3';
15 | import type { NitroApp } from 'nitropack';
16 | import { createProvider, type HTTPMethod } from 'nixle';
17 |
18 | export interface Request extends IncomingMessage {}
19 | export interface Response extends ServerResponse {}
20 |
21 | const sameSiteMap = new Map([
22 | ['Strict', 'strict' as const],
23 | ['Lax', 'lax' as const],
24 | ['None', 'none' as const],
25 | ]);
26 |
27 | export const nitroProvider = createProvider((app) => {
28 | return {
29 | app,
30 | createRoute: ({ method, path, handler }) =>
31 | app.router.use(
32 | path,
33 | defineEventHandler(async (event) => {
34 | return handler({
35 | request: event.node.req,
36 | response: event.node.res,
37 | method: event.method as HTTPMethod,
38 | params: getRouterParams(event),
39 | query: getQuery(event),
40 | body: ['post', 'put', 'patch', 'delete'].includes(method) ? await readBody(event) : {},
41 | redirect: async (url, status) => {
42 | await sendRedirect(event, url, status);
43 | },
44 | setStatusCode: (code) => setResponseStatus(event, code),
45 | setHeader: (key, value) => setHeader(event, key, value),
46 | getHeader: (key) => getHeader(event, key) || null,
47 | headers: Object.fromEntries(
48 | Object.entries(getRequestHeaders(event)).filter(([, v]) => v),
49 | ) as Record,
50 | setCookie: (name, value, options) =>
51 | setCookie(event, name, value, {
52 | ...options,
53 | sameSite: sameSiteMap.get(options?.sameSite || 'Strict') || 'strict',
54 | }),
55 | getCookie: (name) => getCookie(event, name) || null,
56 | });
57 | }),
58 | method,
59 | ),
60 | };
61 | });
62 |
--------------------------------------------------------------------------------
/packages/nixle/src/logger.ts:
--------------------------------------------------------------------------------
1 | import { createConsola, type ConsolaOptions, type LogType, type ConsolaInstance } from 'consola';
2 | import { colorize, type ColorName } from 'consola/utils';
3 | import { createError } from '~/createError';
4 | import { StatusCode } from '.';
5 |
6 | let loggerInstance: ConsolaInstance;
7 |
8 | export type Logger = typeof log;
9 |
10 | export const createLogger = (options: Partial) => {
11 | loggerInstance = createConsola(options);
12 | };
13 |
14 | const _log = (type: LogType, ...messages: any[]) => {
15 | if (!loggerInstance) {
16 | return;
17 | }
18 |
19 | const nixleMessage = `${colorize('bgBlue', ' Nixle ')}`;
20 |
21 | const method = loggerInstance[type];
22 |
23 | if (!method) {
24 | throw createError({
25 | message: `Logger method "${type}" not found`,
26 | statusCode: StatusCode.INTERNAL_SERVER_ERROR,
27 | });
28 | }
29 |
30 | method(`${nixleMessage}`, ...messages);
31 | };
32 |
33 | /**
34 | * @description Log message
35 | *
36 | * @param {string} message
37 | *
38 | * @example log.info('Hello world')
39 | */
40 | export const log = {
41 | info: (...messages: any[]) => _log('info', ...messages),
42 | success: (...messages: any[]) => _log('success', ...messages),
43 | warn: (...messages: any[]) => _log('warn', ...messages),
44 | error: (...messages: any[]) => _log('error', ...messages),
45 | fatal: (...messages: any[]) => _log('fatal', ...messages),
46 | debug: (...messages: any[]) => _log('debug', ...messages),
47 | trace: (...messages: any[]) => _log('trace', ...messages),
48 | silent: (...messages: any[]) => _log('silent', ...messages),
49 | log: (...messages: any[]) => _log('log', ...messages),
50 | fail: (...messages: any[]) => _log('fail', ...messages),
51 | verbose: (...messages: any[]) => _log('verbose', ...messages),
52 | } satisfies Record, (message: string) => void>;
53 |
54 | export const contextLog = (context: string, color: ColorName = 'bgWhite'): typeof log =>
55 | Object.fromEntries(
56 | Object.entries(log).map(([name, log]) => [
57 | name,
58 | (...messages: any[]) => log(colorize(color, ` ${context} `), ...messages),
59 | ]),
60 | ) as typeof log;
61 |
--------------------------------------------------------------------------------
/packages/hono/dist/index.js:
--------------------------------------------------------------------------------
1 | import { createProvider as m } from "nixle";
2 | var h = decodeURIComponent, f = /^[\w!#$%&'*.^`|~+-]+$/, l = /^[ !#-:<-[\]-~]*$/, d = (t, s) => t.trim().split(";").reduce((r, n) => {
3 | n = n.trim();
4 | const i = n.indexOf("=");
5 | if (i === -1)
6 | return r;
7 | const a = n.substring(0, i).trim();
8 | if (s && s !== a || !f.test(a))
9 | return r;
10 | let o = n.substring(i + 1).trim();
11 | return o.startsWith('"') && o.endsWith('"') && (o = o.slice(1, -1)), l.test(o) && (r[a] = h(o)), r;
12 | }, {}), c = (t, s, e = {}) => {
13 | let r = `${t}=${s}`;
14 | return e && typeof e.maxAge == "number" && e.maxAge >= 0 && (r += `; Max-Age=${Math.floor(e.maxAge)}`), e.domain && (r += `; Domain=${e.domain}`), e.path && (r += `; Path=${e.path}`), e.expires && (r += `; Expires=${e.expires.toUTCString()}`), e.httpOnly && (r += "; HttpOnly"), e.secure && (r += "; Secure"), e.sameSite && (r += `; SameSite=${e.sameSite}`), e.partitioned && (r += "; Partitioned"), r;
15 | }, g = (t, s, e = {}) => (s = encodeURIComponent(s), c(t, s, e)), v = (t, s) => {
16 | const e = t.req.raw.headers.get("Cookie");
17 | return typeof s == "string" ? e ? d(e, s)[s] : void 0 : e ? d(e) : {};
18 | }, q = (t, s, e, r) => {
19 | const n = g(s, e, { path: "/", ...r });
20 | t.header("set-cookie", n, { append: !0 });
21 | };
22 | const C = m((t) => ({
23 | app: t,
24 | createRoute: ({ method: s, path: e, handler: r }) => {
25 | ({
26 | get: t.get,
27 | post: t.post,
28 | put: t.put,
29 | patch: t.patch,
30 | delete: t.delete,
31 | options: t.options
32 | })[s](e, async (i) => i.json(
33 | await r({
34 | request: i.req,
35 | response: i.res,
36 | method: i.req.method,
37 | params: i.req.param() || {},
38 | query: i.req.query() || {},
39 | body: ["post", "put", "patch", "delete"].includes(s) ? await i.req.json() : {},
40 | redirect: async (a, o) => {
41 | await i.redirect(a, o);
42 | },
43 | setStatusCode: (a) => i.status(a),
44 | setHeader: (a, o) => i.header(a, o),
45 | getHeader: (a) => i.req.header(a) || null,
46 | headers: i.req.header(),
47 | setCookie: (a, o, u) => q(i, a, o, u),
48 | getCookie: (a) => v(i, a) || null
49 | })
50 | ));
51 | }
52 | }));
53 | export {
54 | C as honoProvider
55 | };
56 |
--------------------------------------------------------------------------------
/packages/nixle/src/router/createRoute.ts:
--------------------------------------------------------------------------------
1 | import type { ValidPath } from '~/utils/types';
2 | import type { HTTPMethod, RouteHandler, RouteOptions } from '..';
3 | import { validatePath } from '~/utils/validations';
4 |
5 | interface Route<
6 | Path extends string = string,
7 | Method extends HTTPMethod = HTTPMethod,
8 | Params extends {} = any,
9 | Query extends {} = any,
10 | Body extends {} = any,
11 | Response extends unknown = unknown,
12 | Options extends RouteOptions = RouteOptions<
13 | Params,
14 | Query,
15 | Body,
16 | Response
17 | >,
18 | > {
19 | path: Path;
20 | method: Method;
21 | options: Options;
22 | $infer: {
23 | path: Path;
24 | method: Method;
25 | params: Awaited;
26 | query: Awaited;
27 | body: Awaited;
28 | response: Awaited;
29 | };
30 | }
31 |
32 | function createRoute(method: Method) {
33 | function route<
34 | Path extends string,
35 | Params extends {},
36 | Query extends {},
37 | Body extends {},
38 | Response extends unknown,
39 | >(
40 | path: ValidPath,
41 | options: RouteOptions,
42 | ): Route;
43 | function route(
44 | path: ValidPath,
45 | handler: RouteHandler<{}, {}, {}, Response>,
46 | ): Route;
47 |
48 | function route<
49 | Path extends string,
50 | Params extends {},
51 | Query extends {},
52 | Body extends {},
53 | Response extends unknown,
54 | >(
55 | path: ValidPath,
56 | optionsOrHandler:
57 | | RouteOptions
58 | | RouteHandler,
59 | ): Route, Method, Params, Query, Body, Response> {
60 | validatePath(path);
61 |
62 | const options =
63 | typeof optionsOrHandler === 'function' ? { handler: optionsOrHandler } : optionsOrHandler;
64 |
65 | return {
66 | path,
67 | method,
68 | options,
69 | $infer: {} as any,
70 | };
71 | }
72 |
73 | return route;
74 | }
75 |
76 | const route = {
77 | get: createRoute('GET'),
78 | post: createRoute('POST'),
79 | patch: createRoute('PATCH'),
80 | put: createRoute('PUT'),
81 | delete: createRoute('DELETE'),
82 | options: createRoute('OPTIONS'),
83 | };
84 |
85 | export { route };
86 | export type { Route };
87 |
--------------------------------------------------------------------------------
/packages/nixle/src/types/CookieOptions.ts:
--------------------------------------------------------------------------------
1 | export interface CookieOptions {
2 | /**
3 | * The url path prefix must be absolute. It makes the cookie accessible for pages under that path.
4 | * By default, it’s the current path.
5 | *
6 | * @example
7 | * '/admin'
8 | */
9 | path?: string;
10 | /**
11 | * A domain defines where the cookie is accessible. In practice though, there are limitations. We can’t set any domain.
12 | *
13 | * @example
14 | * 'site.com'
15 | */
16 | domain?: string;
17 | /**
18 | * By default, if a cookie doesn’t have one of this option, it disappears when the browser is closed.
19 | *
20 | * @example
21 | * new Date('2021-01-01')
22 | */
23 | expires?: Date;
24 | /**
25 | * It’s an alternative to expires and specifies the cookie’s expiration in seconds from the current moment.
26 | * If set to zero or a negative value, the cookie is deleted.
27 | *
28 | * @example
29 | * 60 * 60 * 24 * 7 // 7 days
30 | */
31 | maxAge?: number;
32 | /**
33 | * The cookie should be transferred only over HTTPS.
34 | */
35 | secure?: boolean;
36 | /**
37 | * It’s designed to protect from so-called XSRF (cross-site request forgery) attacks.
38 | *
39 | * @summary
40 | * Strict
41 | * Means that the browser sends the cookie only for same-site requests, that is, requests originating from the same site that set the cookie. If a request originates from a different domain or scheme (even with the same domain), no cookies with the SameSite=Strict attribute are sent.
42 | *
43 | * Lax
44 | * Means that the cookie is not sent on cross-site requests, such as on requests to load images or frames, but is sent when a user is navigating to the origin site from an external site (for example, when following a link). This is the default behavior if the SameSite attribute is not specified.
45 | *
46 | * None
47 | * means that the browser sends the cookie with both cross-site and same-site requests. The Secure attribute must also be set when setting this value, like so SameSite=None; Secure. If Secure is missing an error will be logged:
48 | *
49 | * Cookie "myCookie" rejected because it has the "SameSite=None" attribute but is missing the "secure" attribute.
50 | *
51 | * This Set-Cookie was blocked because it had the "SameSite=None" attribute but did not have the "Secure" attribute, which is required in order to use "SameSite=None".
52 | */
53 | sameSite?: 'Strict' | 'Lax' | 'None';
54 | /**
55 | * This option forbids any JavaScript access to the cookie.
56 | * We can’t see such a cookie or manipulate it using `document.cookie`.
57 | */
58 | httpOnly?: boolean;
59 | }
60 |
--------------------------------------------------------------------------------
/docs/plugins/zod.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Zod
6 |
7 | [zod](https://www.npmjs.com/package/zod) is a TypeScript-first schema declaration and validation library. It is used to validate the request body, query, and params.
8 |
9 | ## Install
10 |
11 | You can install the `@nixle/zod` package using npm, pnpm, yarn, or bun:
12 |
13 | ::: code-group
14 |
15 | ```sh [npm]
16 | npm i @nixle/zod
17 | ```
18 |
19 | ```sh [pnpm]
20 | pnpm add @nixle/zod
21 | ```
22 |
23 | ```sh [yarn]
24 | yarn add @nixle/zod
25 | ```
26 |
27 | ```sh [bun]
28 | bun i @nixle/zod
29 | ```
30 |
31 | :::
32 |
33 | ## Setup
34 |
35 | To use zod validation, you need to add the `zodPlugin` to the `plugins` array when creating the app.
36 |
37 | ```ts
38 | import { createApp } from 'nixle';
39 | import { zodPlugin } from '@nixle/zod';
40 |
41 | const app = createApp({
42 | plugins: [zodPlugin],
43 | });
44 | ```
45 |
46 | ## Usage
47 |
48 | To use zod validation, you can use the `zodObject` parameter.
49 |
50 | ### Routers
51 |
52 | In the router, you can use the `zodObject` parameter to validate the request information.
53 |
54 | ```ts
55 | import { createRouter } from 'nixle';
56 |
57 | const usersRouter = createRouter('/users', ({ route, zodObject }) => [
58 | route.post('/:id', {
59 | paramsValidation: zodObject((z) => ({
60 | id: z.string(),
61 | })).validate,
62 | queryValidation: zodObject((z) => ({
63 | page: z.string(),
64 | })).validate,
65 | bodyValidation: zodObject((z) => ({
66 | name: z.string(),
67 | })).validate,
68 | handler: ({ params, query, body }) => 'Hello World!',
69 | }),
70 | ]);
71 | ```
72 |
73 | ### Services
74 |
75 | In the service, you can use the `zodObject` parameter to validate any object.
76 |
77 | ```ts
78 | import { createService } from 'nixle';
79 |
80 | const usersService = createService('users', ({ zodObject }) => {
81 | const updateUser = async (user) => {
82 | const { validate } = zodObject((z) => ({
83 | id: z.string(),
84 | name: z.string(),
85 | }));
86 |
87 | const validatedUser = await validate(user);
88 |
89 | // Save the user to the database
90 |
91 | return validatedUser;
92 | };
93 |
94 | return {
95 | updateUser,
96 | };
97 | });
98 | ```
99 |
100 | ## TypeScript
101 |
102 | To get the type of the validated object, you can use the `$infer` property.
103 |
104 | ```ts
105 | import { createRouter } from 'nixle';
106 | import { zodObject } from '@nixle/zod';
107 |
108 | const user = zodObject((z) => ({
109 | id: z.string(),
110 | name: z.string(),
111 | }));
112 |
113 | type User = typeof user.$infer;
114 |
115 | // { id: string; name: string; }
116 | ```
117 |
--------------------------------------------------------------------------------
/packages/nixle/dist/types/CookieOptions.d.ts:
--------------------------------------------------------------------------------
1 | export interface CookieOptions {
2 | /**
3 | * The url path prefix must be absolute. It makes the cookie accessible for pages under that path.
4 | * By default, it’s the current path.
5 | *
6 | * @example
7 | * '/admin'
8 | */
9 | path?: string;
10 | /**
11 | * A domain defines where the cookie is accessible. In practice though, there are limitations. We can’t set any domain.
12 | *
13 | * @example
14 | * 'site.com'
15 | */
16 | domain?: string;
17 | /**
18 | * By default, if a cookie doesn’t have one of this option, it disappears when the browser is closed.
19 | *
20 | * @example
21 | * new Date('2021-01-01')
22 | */
23 | expires?: Date;
24 | /**
25 | * It’s an alternative to expires and specifies the cookie’s expiration in seconds from the current moment.
26 | * If set to zero or a negative value, the cookie is deleted.
27 | *
28 | * @example
29 | * 60 * 60 * 24 * 7 // 7 days
30 | */
31 | maxAge?: number;
32 | /**
33 | * The cookie should be transferred only over HTTPS.
34 | */
35 | secure?: boolean;
36 | /**
37 | * It’s designed to protect from so-called XSRF (cross-site request forgery) attacks.
38 | *
39 | * @summary
40 | * Strict
41 | * Means that the browser sends the cookie only for same-site requests, that is, requests originating from the same site that set the cookie. If a request originates from a different domain or scheme (even with the same domain), no cookies with the SameSite=Strict attribute are sent.
42 | *
43 | * Lax
44 | * Means that the cookie is not sent on cross-site requests, such as on requests to load images or frames, but is sent when a user is navigating to the origin site from an external site (for example, when following a link). This is the default behavior if the SameSite attribute is not specified.
45 | *
46 | * None
47 | * means that the browser sends the cookie with both cross-site and same-site requests. The Secure attribute must also be set when setting this value, like so SameSite=None; Secure. If Secure is missing an error will be logged:
48 | *
49 | * Cookie "myCookie" rejected because it has the "SameSite=None" attribute but is missing the "secure" attribute.
50 | *
51 | * This Set-Cookie was blocked because it had the "SameSite=None" attribute but did not have the "Secure" attribute, which is required in order to use "SameSite=None".
52 | */
53 | sameSite?: 'Strict' | 'Lax' | 'None';
54 | /**
55 | * This option forbids any JavaScript access to the cookie.
56 | * We can’t see such a cookie or manipulate it using `document.cookie`.
57 | */
58 | httpOnly?: boolean;
59 | }
60 |
--------------------------------------------------------------------------------
/docs/plugins/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Plugins
6 |
7 | To extend Nixle, you can create a plugin. The plugin is a function that extends the functionality of the application. It is defined by a pair: a name (a string) and a plugin function.
8 |
9 | ## Creating a plugin
10 |
11 | To create a plugin, you need to create a function that accepts an application and returns a new application. The plugin function can be asynchronous.
12 |
13 | ```ts
14 | import { createPlugin } from 'nixle';
15 |
16 | export const myPlugin = createPlugin('myPlugin', async ({ nixleApp, log }) => {
17 | log.info('Hello from my plugin!');
18 | });
19 | ```
20 |
21 | Available options:
22 |
23 | - `nixleApp` - the application instance
24 | - `log` - a function that logs a message to the console
25 |
26 | ### Extending the application
27 |
28 | There are two ways to extend the application:
29 |
30 | #### Router
31 |
32 | You can extend the router options by using function `extendRouterContext`:
33 |
34 | ```ts
35 | import { createPlugin } from 'nixle';
36 |
37 | // To create TypeScript definitions
38 | declare global {
39 | namespace Nixle {
40 | interface RouterContext {
41 | someOption: string;
42 | }
43 | }
44 | }
45 |
46 | export const myPlugin = createPlugin('myPlugin', async ({ extendRouterContext }) => {
47 | extendRouterContext({ someOption: 'someValue' });
48 | });
49 | ```
50 |
51 | And then you can use the `someOption` in the router:
52 |
53 | ```ts
54 | import { createRouter } from 'nixle';
55 |
56 | const app = createRouter('/users', ({ route, log, someOption }) => [
57 | route.get('/', () => {
58 | log.info(someOption);
59 | }),
60 | ]);
61 | ```
62 |
63 | #### Services
64 |
65 | You can extend the services by using function `extendServiceContext`:
66 |
67 | ```ts
68 | import { createPlugin } from 'nixle';
69 |
70 | // To create TypeScript definitions
71 | declare global {
72 | namespace Nixle {
73 | interface ServiceContext {
74 | someOption: string;
75 | }
76 | }
77 | }
78 |
79 | export const myPlugin = createPlugin('myPlugin', async ({ extendServiceContext }) => {
80 | extendServiceContext({ someOption: 'someValue' });
81 | });
82 | ```
83 |
84 | And then you can use the `someOption` in the service:
85 |
86 | ```ts
87 | import { createService } from 'nixle';
88 |
89 | const app = createService('users', ({ log, someOption }) => {
90 | const getUsers = async () => {
91 | log.info(someOption);
92 | };
93 |
94 | return {
95 | getUsers,
96 | };
97 | });
98 | ```
99 |
100 | ## Using a plugin
101 |
102 | To use a plugin, you need to import it and pass it to the `plugins` option of the `createApp` function.
103 |
104 | ```ts
105 | import { createApp } from 'nixle';
106 | import { myPlugin } from './myPlugin';
107 |
108 | createApp({
109 | plugins: [myPlugin],
110 | });
111 | ```
112 |
--------------------------------------------------------------------------------
/docs/overview/services.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Services
6 |
7 | Services are optional, but highly recommended, components that provide additional functionality to your application. With services, you can separate your application's logic into smaller, more manageable pieces.
8 |
9 | ## Creating
10 |
11 | To create a service, you need to use the `createService` function. This function one argument, which a function that returns an object with the service's methods.
12 |
13 | ```ts
14 | import { createService } from 'nixle';
15 |
16 | export const usersService = createService('users', () => {
17 | const getUsers = () => {
18 | return ['John', 'Jane'];
19 | };
20 |
21 | return { getUsers };
22 | });
23 | ```
24 |
25 | ## Usage
26 |
27 | To use a service, you need to call the returned function from the `createService` function.
28 |
29 | ```ts
30 | import { createRouter } from 'nixle';
31 | import { usersService } from './usersService';
32 |
33 | export const usersRouter = createRouter('/users', ({ route }) => [
34 | route.get('/', () => {
35 | const users = await usersService().getUsers();
36 |
37 | return users;
38 | }),
39 | ]);
40 | ```
41 |
42 | ## Parameters
43 |
44 | You can destruct the `params` object to get some useful parameters.
45 |
46 | ```ts
47 | import { createService } from 'nixle';
48 |
49 | export const usersService = createService('users', ({ log, env }) => {
50 | const getUsers = () => {
51 | log.info('Getting users from site', env.SITE_URL);
52 | return ['John', 'Jane'];
53 | };
54 |
55 | return { getUsers };
56 | });
57 | ```
58 |
59 | ### More
60 |
61 | To use more parameters, you can install additional plugins. For example, the `@nixle/zod` plugin adds the `zodObject` parameter that allows you to validate any object.
62 |
63 | ```ts
64 | import { createService } from 'nixle';
65 |
66 | export const usersService = createService('users', ({ zodObject }) => {
67 | const getUsers = (user) => {
68 | const { validate } = zodObject((zod) => ({
69 | email: zod.string().email(),
70 | password: zod.string().min(8),
71 | }));
72 |
73 | return validate(user);
74 | };
75 |
76 | return { getUsers };
77 | });
78 | ```
79 |
80 | ## TypeScript
81 |
82 | Services are fully typed. You can use the `$inferMethods` and `$inferReturns` properties to infer the service's methods and return types.
83 |
84 | ```ts
85 | import { createService } from 'nixle';
86 |
87 | export const usersService = createService('users', () => {
88 | const getUsers = () => {
89 | return ['John', 'Jane'];
90 | };
91 |
92 | return { getUsers };
93 | });
94 |
95 | type UsersServiceReturns = typeof usersService.$inferMethods;
96 | // type UsersServiceReturns = {
97 | // getUsers: () => string[];
98 | // }
99 |
100 | type UsersServiceReturns = typeof usersService.$inferReturns;
101 | // type UsersServiceReturns = {
102 | // getUsers: string[];
103 | // }
104 | ```
105 |
--------------------------------------------------------------------------------
/packages/nixle/src/router/createRouter.ts:
--------------------------------------------------------------------------------
1 | import { contextLog, log } from '~/logger';
2 | import { type Route, route } from './createRoute';
3 | import { StatusCode, createError, type RouteHandlerContext } from '..';
4 | import type { Guard } from '~/createGuard';
5 | import { getEnv } from '~/env';
6 | import type { ValidPath } from '~/utils/types';
7 | import { validatePath } from '~/utils/validations';
8 | import type { Middleware } from '~/createMiddleware';
9 |
10 | const routerContext: Nixle.RouterContext = {};
11 |
12 | function extendRouterContext(context: T) {
13 | Object.assign(routerContext, context);
14 | }
15 |
16 | export interface RouterContext extends Nixle.RouterContext {
17 | route: typeof route;
18 | log: typeof log;
19 | env: RouteHandlerContext['env'];
20 | }
21 |
22 | interface RouterRoutesHandler {
23 | (context: RouterContext): Routes;
24 | }
25 |
26 | interface RouterOptions {
27 | middlewares?: Middleware[];
28 | guards?: Guard[];
29 | routes: RouterRoutesHandler;
30 | }
31 |
32 | export type ConvertRoutes = {
33 | [P in T[number]['path']]: {
34 | [M in Extract as M['method']]: Omit<
35 | Extract['$infer'],
36 | 'path' | 'method'
37 | >;
38 | };
39 | };
40 |
41 | export interface Router {
42 | path: Path;
43 | middlewares: Middleware[];
44 | guards: Guard[];
45 | routes: () => Routes;
46 | $inferRoutes: Routes extends Route[] ? ConvertRoutes : never;
47 | }
48 |
49 | function createRouter(
50 | path: ValidPath,
51 | options: RouterOptions,
52 | ): Router;
53 | function createRouter(
54 | path: ValidPath,
55 | routes: RouterRoutesHandler,
56 | ): Router;
57 |
58 | function createRouter(
59 | path: ValidPath,
60 | optionsOrRoutes?: RouterOptions | RouterRoutesHandler,
61 | ): Router, Routes> {
62 | validatePath(path);
63 |
64 | const isObject = typeof optionsOrRoutes === 'object';
65 |
66 | if (!optionsOrRoutes || (isObject && !optionsOrRoutes.routes)) {
67 | throw createError('Routes are required', StatusCode.INTERNAL_SERVER_ERROR);
68 | }
69 |
70 | const _routesFunction: RouterRoutesHandler = isObject
71 | ? optionsOrRoutes.routes
72 | : optionsOrRoutes;
73 | const middlewares = isObject ? optionsOrRoutes.middlewares || [] : [];
74 | const guards = isObject ? optionsOrRoutes.guards || [] : [];
75 |
76 | const formatRoutes = () => {
77 | return _routesFunction({
78 | route,
79 | log: contextLog(path, 'bgGreen'),
80 | env: getEnv(),
81 | ...routerContext,
82 | });
83 | };
84 |
85 | return {
86 | path,
87 | routes: formatRoutes,
88 | middlewares,
89 | guards,
90 | $inferRoutes: {} as any,
91 | };
92 | }
93 |
94 | export { createRouter, extendRouterContext };
95 |
--------------------------------------------------------------------------------
/docs/.vitepress/config.mts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitepress';
2 |
3 | const providers = [
4 | { text: 'What is Provider?', link: '/providers/what' },
5 | { text: 'Nuxt', link: '/providers/nuxt' },
6 | { text: 'Express', link: '/providers/express' },
7 | { text: 'Fastify', link: '/providers/fastify' },
8 | { text: 'Elysia (Bun)', link: '/providers/elysia' },
9 | { text: 'Hono', link: '/providers/hono' },
10 | { text: 'Custom', link: '/providers/custom' },
11 | ];
12 |
13 | const overview = [
14 | { text: 'App', link: '/overview/app' },
15 | { text: 'Routers', link: '/overview/routers' },
16 | { text: 'Services', link: '/overview/services' },
17 | { text: 'Logger', link: '/overview/logger' },
18 | { text: 'Errors', link: '/overview/errors' },
19 | { text: 'Environment Variables', link: '/overview/env' },
20 | ];
21 |
22 | const plugins = [
23 | { text: 'Create Plugin', link: '/plugins/custom' },
24 | { text: 'Zod', link: '/plugins/zod' },
25 | { text: 'ofetch', link: '/plugins/ofetch' },
26 | { text: 'CORS', link: '/plugins/cors' },
27 | { text: 'Swagger', link: '/plugins/swagger' },
28 | ];
29 |
30 | // https://vitepress.dev/reference/site-config
31 | export default defineConfig({
32 | title: 'Nixle',
33 | description: 'Universal server-side framework. Backend for everyone and everywhere.',
34 | themeConfig: {
35 | // https://vitepress.dev/reference/default-theme-config
36 | logo: '/logo.svg',
37 | search: {
38 | provider: 'algolia',
39 | options: {
40 | appId: 'DROL3EC08C',
41 | apiKey: '976689f629ffbbec502d2ad03c7f76a6',
42 | indexName: 'nixle',
43 | },
44 | },
45 | nav: [
46 | { text: 'Home', link: '/' },
47 | { text: 'Docs', link: '/introduction/getting-started' },
48 | {
49 | text: 'Providers',
50 | items: providers,
51 | },
52 | {
53 | text: 'Overview',
54 | items: overview,
55 | },
56 | {
57 | text: 'Plugins',
58 | items: plugins,
59 | },
60 | {
61 | text: 'Examples',
62 | link: 'https://github.com/letstri/nixle/tree/main/examples',
63 | },
64 | ],
65 | sidebar: [
66 | {
67 | text: 'Introduction',
68 | items: [
69 | { text: 'Why Nixle?', link: '/introduction/why' },
70 | { text: 'Getting Started', link: '/introduction/getting-started' },
71 | { text: 'Roadmap', link: '/introduction/roadmap' },
72 | ],
73 | },
74 | {
75 | text: 'Providers',
76 | items: providers,
77 | },
78 | {
79 | text: 'Overview',
80 | items: overview,
81 | },
82 | {
83 | text: 'Plugins',
84 | items: plugins,
85 | },
86 | {
87 | text: 'Examples',
88 | link: 'https://github.com/letstri/nixle/tree/main/examples',
89 | },
90 | ],
91 | socialLinks: [
92 | {
93 | icon: 'github',
94 | link: 'https://github.com/letstri/nixle',
95 | },
96 | {
97 | icon: 'twitter',
98 | link: 'https://twitter.com/nixlejs',
99 | },
100 | ],
101 | },
102 | });
103 |
--------------------------------------------------------------------------------
/packages/nixle/src/createApp.ts:
--------------------------------------------------------------------------------
1 | import type { ConsolaOptions } from 'consola';
2 | import { createLogger, log } from './logger';
3 | import type dotenv from 'dotenv';
4 |
5 | import type { Provider } from './provider/createProvider';
6 | import { createError, logError } from './createError';
7 | import { hooks } from './hooks';
8 | import type { Plugin } from './plugins/createPlugin';
9 | import { buildPlugins } from './plugins/buildPlugins';
10 | import { buildEnv } from './env';
11 | import { StatusCode, type Router } from '.';
12 | import { buildRouter } from './router/buildRouter';
13 | import { validatePath } from './utils/validations';
14 | import { pick } from './utils/helpers';
15 | import { createMiddleware, type Middleware } from './createMiddleware';
16 | import type { Module } from './createModule';
17 |
18 | type ConvertModules = M[number]['routers'];
19 |
20 | type ConvertRouters = {
21 | [P in R[number]['path']]: Extract['$inferRoutes'];
22 | };
23 |
24 | export interface AppOptions<
25 | Modules extends Module[] = Module[],
26 | Routers extends Router[] = Router[],
27 | P = any,
28 | > {
29 | provider: Provider;
30 | routers?: Routers;
31 | modules?: Modules;
32 | plugins?: Plugin[];
33 | middlewares?: Middleware[];
34 | logger?: Partial | false;
35 | env?: dotenv.DotenvConfigOptions;
36 | globalPrefix?: string;
37 | }
38 |
39 | export type NixleApp = ReturnType;
40 |
41 | export function createApp(
42 | options: AppOptions,
43 | ) {
44 | if (options.globalPrefix) {
45 | validatePath(options.globalPrefix);
46 | }
47 |
48 | if (options.logger !== false) {
49 | createLogger(options.logger || {});
50 | }
51 |
52 | try {
53 | if (!options.provider) {
54 | throw createError('Provider is required', StatusCode.INTERNAL_SERVER_ERROR);
55 | }
56 | if (
57 | (!options.routers && !options.modules) ||
58 | (options.routers?.length === 0 && options.modules?.length === 0)
59 | ) {
60 | throw createError('At least one router is required', StatusCode.INTERNAL_SERVER_ERROR);
61 | }
62 | } catch (e) {
63 | logError(e, log);
64 | process.exit(1);
65 | }
66 |
67 | buildEnv(options.env);
68 |
69 | if (options.plugins) {
70 | buildPlugins(options.provider, options);
71 | }
72 |
73 | options.middlewares = [
74 | createMiddleware('nixle-global-middleware', ({ setHeader }) => {
75 | setHeader('X-Powered-By', 'Nixle');
76 | }),
77 | ...(options.middlewares || []),
78 | ];
79 |
80 | options.modules?.forEach((module) => {
81 | module.routers.forEach((router) => {
82 | buildRouter(options, router);
83 | });
84 | });
85 |
86 | options.routers?.forEach((router) => {
87 | buildRouter(options, router);
88 | });
89 |
90 | const app = {
91 | app: options.provider.app,
92 | hooks: pick(hooks, ['afterEach', 'beforeEach', 'callHook', 'hook', 'hookOnce']),
93 | $inferRouters: {} as ConvertRouters & ConvertRouters>,
94 | };
95 |
96 | log.success('🔥 Application successfully started');
97 |
98 | return app;
99 | }
100 |
--------------------------------------------------------------------------------
/docs/overview/env.md:
--------------------------------------------------------------------------------
1 | # Environment Variables
2 |
3 | Environment variables are a way to pass configuration to your service. They are set in the `env` object passed to your service's factory function.
4 |
5 | ## Setup
6 |
7 | ### .env
8 |
9 | To use environment variables, you must first define them in your .env file. For example:
10 |
11 | ```sh
12 | SOME_SERVICE_URL=https://some-service.com
13 | ```
14 |
15 | ### TypeScript
16 |
17 | If you are using TypeScript, you must also define the types for your environment variables. For example:
18 |
19 | ```ts
20 | declare global {
21 | namespace Nixle {
22 | interface Env {
23 | SOME_SERVICE_URL: string;
24 | }
25 | }
26 | }
27 | ```
28 |
29 | ### Options
30 |
31 | You can also pass options to the `createApp`.
32 |
33 | ```ts
34 | import { createApp } from 'nixle';
35 |
36 | const app = createApp({
37 | env: {
38 | path: '.env',
39 | encoding: 'utf8',
40 | debug: false,
41 | override: false,
42 | processEnv: process.env,
43 | DOTENV_KEY: undefined,
44 | },
45 | });
46 | ```
47 |
48 | The options are defined as follows:
49 |
50 | ```ts
51 | interface DotenvConfigOptions {
52 | /**
53 | * Default: `path.resolve(process.cwd(), '.env')`
54 | *
55 | * Specify a custom path if your file containing environment variables is located elsewhere.
56 | *
57 | * example: `{ path: '/custom/path/to/.env' }`
58 | */
59 | path?: string | URL;
60 |
61 | /**
62 | * Default: `utf8`
63 | *
64 | * Specify the encoding of your file containing environment variables.
65 | *
66 | * example: `{ encoding: 'latin1' }`
67 | */
68 | encoding?: string;
69 |
70 | /**
71 | * Default: `false`
72 | *
73 | * Turn on logging to help debug why certain keys or values are not being set as you expect.
74 | *
75 | * example: `{ debug: process.env.DEBUG }`
76 | */
77 | debug?: boolean;
78 |
79 | /**
80 | * Default: `false`
81 | *
82 | * Override any environment variables that have already been set on your machine with values from your .env file.
83 | *
84 | * example: `{ override: true }`
85 | */
86 | override?: boolean;
87 |
88 | /**
89 | * Default: `process.env`
90 | *
91 | * Specify an object to write your secrets to. Defaults to process.env environment variables.
92 | *
93 | * example: `const processEnv = {}; { processEnv: processEnv }`
94 | */
95 | processEnv?: DotenvPopulateInput;
96 |
97 | /**
98 | * Default: `undefined`
99 | *
100 | * Pass the DOTENV_KEY directly to config options. Defaults to looking for process.env.DOTENV_KEY environment variable. Note this only applies to decrypting .env.vault files. If passed as null or undefined, or not passed at all, dotenv falls back to its traditional job of parsing a .env file.
101 | *
102 | * example: `{ DOTENV_KEY: 'dotenv://:key_1234…@dotenv.org/vault/.env.vault?environment=production' }`
103 | */
104 | DOTENV_KEY?: string;
105 | }
106 | ```
107 |
108 | ## Usage
109 |
110 | ### Services
111 |
112 | Then, you can access them in your service's factory function:
113 |
114 | ```ts
115 | import { createService } from 'nixle';
116 |
117 | const app = createService('users', ({ env }) => {
118 | const getUsers = async () => {
119 | const users = await fetch(`${env.SOME_SERVICE_URL}/users`).then((res) => res.json());
120 |
121 | return users;
122 | };
123 |
124 | return {
125 | getUsers,
126 | };
127 | });
128 | ```
129 |
130 | ### Routes
131 |
132 | You can also access environment variables in your routes:
133 |
134 | ```ts
135 | import { createRouter } from 'nixle';
136 |
137 | const app = createRoute('/users', ({ route, env }) => [
138 | route.get('/', () => {
139 | return env.SOME_SERVICE_URL;
140 | }),
141 | ]);
142 | ```
143 |
--------------------------------------------------------------------------------
/docs/public/logo-with-text.svg:
--------------------------------------------------------------------------------
1 |
45 |
--------------------------------------------------------------------------------
/packages/nixle/src/provider/RouteHandler.ts:
--------------------------------------------------------------------------------
1 | import type { CookieOptions, HTTPMethod, StatusCode } from '..';
2 |
3 | export interface ProviderRouteHandlerContext<
4 | P extends Record = Record,
5 | Q extends Record = Record,
6 | B extends Record = Record,
7 | > {
8 | /**
9 | * Request
10 | *
11 | * @deprecated Try to not use it, if you need some features from the request, please create an issue.
12 | * @see https://github.com/letstri/nixle/issues
13 | */
14 | request: any;
15 | /**
16 | * Response
17 | *
18 | * @deprecated Try to not use it, if you need some features from the response, please create an issue.
19 | * @see https://github.com/letstri/nixle/issues
20 | */
21 | response: any;
22 | /**
23 | * HTTP method
24 | */
25 | method: HTTPMethod;
26 | /**
27 | * Path parameters
28 | *
29 | * @example
30 | * // We have a path '/users/:id'
31 | * // And we have a request '/users/123'
32 | *
33 | * // After that we can get params:
34 | * // => { id: '123' }
35 | */
36 | params: Awaited;
37 | /**
38 | * Query parameters
39 | *
40 | * @example
41 | * // We have a request with query ?name=John
42 | *
43 | * // After that we can get query:
44 | * // => { name: 'John' }
45 | */
46 | query: Awaited;
47 | /**
48 | * Body parameters
49 | *
50 | * @example
51 | * // We have a request with body { name: 'John' }
52 | *
53 | * // After that we can get body:
54 | * // => { name: 'John' }
55 | */
56 | body: Awaited;
57 | /**
58 | * Set status code
59 | *
60 | * @param code
61 | * @default 200
62 | *
63 | * @example
64 | * setStatusCode(StatusCodes.BAD_REQUEST);
65 | */
66 | setStatusCode: (code: StatusCode) => void;
67 | /**
68 | * Headers
69 | *
70 | * @readonly
71 | */
72 | headers: Readonly>;
73 | /**
74 | * Get header
75 | *
76 | * @param key
77 | *
78 | * @example
79 | * getHeader('Content-Type'); // -> application/json
80 | */
81 | getHeader: (key: string) => string | null;
82 | /**
83 | * Set header
84 | *
85 | * @param key
86 | * @param value
87 | *
88 | * @example
89 | * setHeader('Content-Type', 'application/json');
90 | */
91 | setHeader: (key: string, value: string) => void;
92 | /**
93 | * Set cookie
94 | *
95 | * @param key
96 | * @param value
97 | * @param options
98 | *
99 | * @example
100 | * setCookie('token', '123');
101 | * setCookie('token', '123', { httpOnly: true });
102 | */
103 | setCookie: (key: string, value: string, options?: CookieOptions) => void;
104 | /**
105 | * Get cookie
106 | *
107 | * @param key
108 | *
109 | * @example
110 | * getCookie('token'); // -> 123
111 | */
112 | getCookie: (key: string) => string | null;
113 | /**
114 | * Redirect
115 | *
116 | * @param url
117 | * @param statusCode
118 | *
119 | * @example
120 | * redirect('/users', StatusCode.PERMANENT_REDIRECT);
121 | */
122 | redirect: (
123 | url: string,
124 | statusCode?:
125 | | StatusCode.MOVED_PERMANENTLY
126 | | StatusCode.MOVED_TEMPORARILY
127 | | StatusCode.PERMANENT_REDIRECT
128 | | StatusCode.TEMPORARY_REDIRECT,
129 | ) => void | Promise;
130 | }
131 |
132 | export interface ProviderRouteHandler<
133 | P extends Record = Record,
134 | Q extends Record = Record,
135 | B extends Record = Record,
136 | R extends unknown = unknown,
137 | > {
138 | (context: ProviderRouteHandlerContext): R;
139 | }
140 |
--------------------------------------------------------------------------------
/packages/nixle/dist/router/createRoute.d.ts:
--------------------------------------------------------------------------------
1 | import type { ValidPath } from '../utils/types';
2 | import type { HTTPMethod, RouteHandler, RouteOptions } from '..';
3 | interface Route = RouteOptions> {
4 | path: Path;
5 | method: Method;
6 | options: Options;
7 | $infer: {
8 | path: Path;
9 | method: Method;
10 | params: Awaited;
11 | query: Awaited;
12 | body: Awaited;
13 | response: Awaited;
14 | };
15 | }
16 | declare const route: {
17 | get: {
18 | (path: ValidPath, options: RouteOptions): Route>;
19 | (path: ValidPath, handler: RouteHandler<{}, {}, {}, Response>): Route>;
20 | };
21 | post: {
22 | (path: ValidPath, options: RouteOptions): Route>;
23 | (path: ValidPath, handler: RouteHandler<{}, {}, {}, Response>): Route>;
24 | };
25 | patch: {
26 | (path: ValidPath, options: RouteOptions): Route>;
27 | (path: ValidPath, handler: RouteHandler<{}, {}, {}, Response>): Route>;
28 | };
29 | put: {
30 | (path: ValidPath, options: RouteOptions): Route>;
31 |