├── .gitignore ├── examples ├── elysia │ ├── .gitignore │ ├── package.json │ └── index.ts ├── hono │ ├── .gitignore │ ├── package.json │ └── index.mjs ├── express │ ├── .gitignore │ ├── package.json │ └── index.mjs ├── fastify │ ├── .gitignore │ ├── package.json │ └── index.mjs └── nuxt │ ├── server │ ├── tsconfig.json │ └── plugins │ │ └── nixle.ts │ ├── app.vue │ ├── public │ └── favicon.ico │ ├── tsconfig.json │ ├── nuxt.config.ts │ ├── .gitignore │ ├── package.json │ └── README.md ├── docs ├── .vitepress │ ├── .gitignore │ ├── theme │ │ ├── index.ts │ │ └── custom.scss │ └── config.mts ├── package.json ├── introduction │ ├── roadmap.md │ ├── why.md │ └── getting-started.md ├── plugins │ ├── cors.md │ ├── swagger.md │ ├── ofetch.md │ ├── zod.md │ └── custom.md ├── index.md ├── public │ ├── logo.svg │ └── logo-with-text.svg ├── providers │ ├── custom.md │ ├── elysia.md │ ├── express.md │ ├── fastify.md │ ├── hono.md │ ├── nuxt.md │ └── what.md └── overview │ ├── app.md │ ├── logger.md │ ├── errors.md │ ├── services.md │ ├── env.md │ └── routers.md ├── pnpm-workspace.yaml ├── .vscode └── extensions.json ├── commitlint.config.js ├── .husky ├── pre-commit └── commit-msg ├── packages ├── nixle │ ├── dist │ │ ├── utils │ │ │ ├── validations.d.ts │ │ │ ├── types.d.ts │ │ │ └── helpers.d.ts │ │ ├── types │ │ │ ├── HTTPMethod.d.ts │ │ │ └── CookieOptions.d.ts │ │ ├── router │ │ │ ├── index.d.ts │ │ │ ├── buildRouter.d.ts │ │ │ ├── createRouter.d.ts │ │ │ ├── createRoute.d.ts │ │ │ └── Route.d.ts │ │ ├── plugins │ │ │ ├── buildPlugins.d.ts │ │ │ └── createPlugin.d.ts │ │ ├── env.d.ts │ │ ├── hooks.d.ts │ │ ├── createModule.d.ts │ │ ├── createGuard.d.ts │ │ ├── provider │ │ │ ├── createProvider.d.ts │ │ │ └── RouteHandler.d.ts │ │ ├── createMiddleware.d.ts │ │ ├── createService.d.ts │ │ ├── index.d.ts │ │ ├── logger.d.ts │ │ ├── createError.d.ts │ │ └── createApp.d.ts │ ├── src │ │ ├── types │ │ │ ├── HTTPMethod.ts │ │ │ └── CookieOptions.ts │ │ ├── router │ │ │ ├── index.ts │ │ │ ├── createRoute.ts │ │ │ ├── createRouter.ts │ │ │ ├── Route.ts │ │ │ └── buildRouter.ts │ │ ├── hooks.ts │ │ ├── utils │ │ │ ├── types.ts │ │ │ ├── validations.ts │ │ │ └── helpers.ts │ │ ├── provider │ │ │ ├── createProvider.ts │ │ │ └── RouteHandler.ts │ │ ├── createModule.ts │ │ ├── plugins │ │ │ ├── buildPlugins.ts │ │ │ └── createPlugin.ts │ │ ├── env.ts │ │ ├── index.ts │ │ ├── createGuard.ts │ │ ├── createMiddleware.ts │ │ ├── createService.ts │ │ ├── logger.ts │ │ ├── createApp.ts │ │ └── createError.ts │ ├── vite.config.ts │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── hono │ ├── vite.config.ts │ ├── tsconfig.json │ ├── dist │ │ ├── index.d.ts │ │ └── index.js │ ├── package.json │ └── src │ │ └── index.ts ├── nitro │ ├── vite.config.ts │ ├── tsconfig.json │ ├── dist │ │ ├── index.d.ts │ │ └── index.js │ ├── package.json │ └── src │ │ └── index.ts ├── zod │ ├── vite.config.ts │ ├── tsconfig.json │ ├── package.json │ ├── dist │ │ ├── index.js │ │ └── index.d.ts │ └── src │ │ └── index.ts ├── elysia │ ├── vite.config.ts │ ├── tsconfig.json │ ├── dist │ │ ├── index.d.ts │ │ └── index.js │ ├── package.json │ └── src │ │ └── index.ts ├── express │ ├── vite.config.ts │ ├── dist │ │ ├── index.d.ts │ │ └── index.js │ ├── tsconfig.json │ ├── package.json │ └── src │ │ └── index.ts ├── fastify │ ├── vite.config.ts │ ├── tsconfig.json │ ├── dist │ │ ├── index.d.ts │ │ └── index.js │ ├── package.json │ └── src │ │ └── index.ts └── ofetch │ ├── vite.config.ts │ ├── dist │ ├── index.js │ └── index.d.ts │ ├── tsconfig.json │ ├── src │ └── index.ts │ └── package.json ├── .prettierrc.json ├── configs ├── tsconfig.base.json └── vite.config.base.ts ├── scripts └── patch-versions.ts ├── LICENSE ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /examples/elysia/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /examples/hono/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /docs/.vitepress/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | cache 3 | -------------------------------------------------------------------------------- /examples/express/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /examples/fastify/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | - 'examples/*' 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /examples/nuxt/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm run build 5 | -------------------------------------------------------------------------------- /examples/nuxt/app.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /packages/nixle/dist/utils/validations.d.ts: -------------------------------------------------------------------------------- 1 | export declare function validatePath(path: string): void; 2 | -------------------------------------------------------------------------------- /examples/nuxt/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letstri/nixle/HEAD/examples/nuxt/public/favicon.ico -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /packages/nixle/dist/types/HTTPMethod.d.ts: -------------------------------------------------------------------------------- 1 | export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS'; 2 | -------------------------------------------------------------------------------- /packages/nixle/src/types/HTTPMethod.ts: -------------------------------------------------------------------------------- 1 | export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS'; 2 | -------------------------------------------------------------------------------- /examples/nuxt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | -------------------------------------------------------------------------------- /packages/nixle/src/router/index.ts: -------------------------------------------------------------------------------- 1 | export type { Route } from './createRoute'; 2 | export * from './createRouter'; 3 | export * from './Route'; 4 | -------------------------------------------------------------------------------- /packages/nixle/dist/router/index.d.ts: -------------------------------------------------------------------------------- 1 | export type { Route } from './createRoute'; 2 | export * from './createRouter'; 3 | export * from './Route'; 4 | -------------------------------------------------------------------------------- /examples/nuxt/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | // https://nuxt.com/docs/api/configuration/nuxt-config 2 | export default defineNuxtConfig({ 3 | devtools: { enabled: true }, 4 | }); 5 | -------------------------------------------------------------------------------- /packages/nixle/src/hooks.ts: -------------------------------------------------------------------------------- 1 | import { createHooks } from 'hookable'; 2 | 3 | export const hooks = createHooks<{ 4 | request: any; 5 | response: any; 6 | error: any; 7 | }>(); 8 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "semi": true, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "printWidth": 100, 7 | "trailingComma": "all" 8 | } 9 | -------------------------------------------------------------------------------- /packages/nixle/dist/router/buildRouter.d.ts: -------------------------------------------------------------------------------- 1 | import type { AppOptions } from '../createApp'; 2 | import { type Router } from '..'; 3 | export declare const buildRouter: (appOptions: AppOptions, router: Router) => void; 4 | -------------------------------------------------------------------------------- /packages/nixle/dist/plugins/buildPlugins.d.ts: -------------------------------------------------------------------------------- 1 | import type { AppOptions } from '../createApp'; 2 | import type { Provider } from '..'; 3 | export declare const buildPlugins: (provider: Provider, options: AppOptions) => void; 4 | -------------------------------------------------------------------------------- /packages/nixle/dist/env.d.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | import type { RouteHandlerContext } from './router'; 3 | export declare const getEnv: () => RouteHandlerContext["env"]; 4 | export declare const buildEnv: (config?: dotenv.DotenvConfigOptions) => void; 5 | -------------------------------------------------------------------------------- /examples/express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express", 3 | "scripts": { 4 | "start": "node ./index.mjs" 5 | }, 6 | "dependencies": { 7 | "@nixle/express": "workspace:^", 8 | "express": "^4.18.2", 9 | "nixle": "workspace:^" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/fastify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fastify", 3 | "scripts": { 4 | "start": "node ./index.mjs" 5 | }, 6 | "dependencies": { 7 | "@nixle/fastify": "workspace:^", 8 | "fastify": "^4.24.3", 9 | "nixle": "workspace:^" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/nixle/dist/hooks.d.ts: -------------------------------------------------------------------------------- 1 | export declare const hooks: import("hookable").Hookable<{ 2 | request: any; 3 | response: any; 4 | error: any; 5 | }, import("hookable").HookKeys<{ 6 | request: any; 7 | response: any; 8 | error: any; 9 | }>>; 10 | -------------------------------------------------------------------------------- /examples/hono/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hono", 3 | "scripts": { 4 | "start": "node ./index.mjs" 5 | }, 6 | "dependencies": { 7 | "@hono/node-server": "^1.3.1", 8 | "@nixle/hono": "workspace:^", 9 | "hono": "latest", 10 | "nixle": "workspace:^" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import type { Theme } from 'vitepress'; 2 | import theme from 'vitepress/theme'; 3 | import { inject } from '@vercel/analytics'; 4 | import './custom.scss'; 5 | 6 | export default { 7 | ...theme, 8 | enhanceApp() { 9 | inject(); 10 | }, 11 | } satisfies Theme; 12 | -------------------------------------------------------------------------------- /packages/hono/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { viteConfig } from '../../configs/vite.config.base'; 3 | 4 | export default defineConfig( 5 | viteConfig({ 6 | name: 'hono', 7 | entry: 'src/index.ts', 8 | package: await import('./package.json'), 9 | }), 10 | ); 11 | -------------------------------------------------------------------------------- /packages/nitro/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { viteConfig } from '../../configs/vite.config.base'; 3 | 4 | export default defineConfig( 5 | viteConfig({ 6 | name: 'nitro', 7 | entry: 'src/index.ts', 8 | package: await import('./package.json'), 9 | }), 10 | ); 11 | -------------------------------------------------------------------------------- /packages/nixle/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { viteConfig } from '../../configs/vite.config.base'; 3 | 4 | export default defineConfig( 5 | viteConfig({ 6 | name: 'nixle', 7 | entry: 'src/index.ts', 8 | package: await import('./package.json'), 9 | }), 10 | ); 11 | -------------------------------------------------------------------------------- /packages/zod/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { viteConfig } from '../../configs/vite.config.base'; 3 | 4 | export default defineConfig( 5 | viteConfig({ 6 | name: 'ofetch', 7 | entry: 'src/index.ts', 8 | package: await import('./package.json'), 9 | }), 10 | ); 11 | -------------------------------------------------------------------------------- /packages/elysia/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { viteConfig } from '../../configs/vite.config.base'; 3 | 4 | export default defineConfig( 5 | viteConfig({ 6 | name: 'elysia', 7 | entry: 'src/index.ts', 8 | package: await import('./package.json'), 9 | }), 10 | ); 11 | -------------------------------------------------------------------------------- /packages/express/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { viteConfig } from '../../configs/vite.config.base'; 3 | 4 | export default defineConfig( 5 | viteConfig({ 6 | name: 'express', 7 | entry: 'src/index.ts', 8 | package: await import('./package.json'), 9 | }), 10 | ); 11 | -------------------------------------------------------------------------------- /packages/fastify/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { viteConfig } from '../../configs/vite.config.base'; 3 | 4 | export default defineConfig( 5 | viteConfig({ 6 | name: 'fastify', 7 | entry: 'src/index.ts', 8 | package: await import('./package.json'), 9 | }), 10 | ); 11 | -------------------------------------------------------------------------------- /packages/ofetch/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { viteConfig } from '../../configs/vite.config.base'; 3 | 4 | export default defineConfig( 5 | viteConfig({ 6 | name: 'ofetch', 7 | entry: 'src/index.ts', 8 | package: await import('./package.json'), 9 | }), 10 | ); 11 | -------------------------------------------------------------------------------- /examples/nuxt/.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .data 4 | .nuxt 5 | .nitro 6 | .cache 7 | dist 8 | 9 | # Node dependencies 10 | node_modules 11 | 12 | # Logs 13 | logs 14 | *.log 15 | 16 | # Misc 17 | .DS_Store 18 | .fleet 19 | .idea 20 | 21 | # Local env files 22 | .env 23 | .env.* 24 | !.env.example 25 | -------------------------------------------------------------------------------- /packages/ofetch/dist/index.js: -------------------------------------------------------------------------------- 1 | import { createPlugin as c } from "nixle"; 2 | import { ofetch as r } from "ofetch"; 3 | const n = (t) => { 4 | const o = r.create(t || {}); 5 | return c("ofetch", ({ extendServiceContext: e }) => { 6 | e({ ofetch: o }); 7 | }); 8 | }; 9 | export { 10 | n as ofetchPlugin 11 | }; 12 | -------------------------------------------------------------------------------- /packages/ofetch/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import { type FetchOptions, type $Fetch } from 'ofetch'; 2 | declare global { 3 | namespace Nixle { 4 | interface ServiceContext { 5 | ofetch: $Fetch; 6 | } 7 | } 8 | } 9 | export declare const ofetchPlugin: (options?: FetchOptions) => import("nixle").Plugin; 10 | -------------------------------------------------------------------------------- /examples/elysia/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elysia", 3 | "scripts": { 4 | "dev": "bun run --watch index.ts" 5 | }, 6 | "dependencies": { 7 | "@nixle/elysia": "workspace:^", 8 | "elysia": "latest", 9 | "nixle": "workspace:^" 10 | }, 11 | "devDependencies": { 12 | "bun-types": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/express/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { Express, Request as ExpressRequest, Response as ExpressResponse } from 'express'; 2 | export interface Request extends ExpressRequest { 3 | } 4 | export interface Response extends ExpressResponse { 5 | } 6 | export declare const expressProvider: (app: Express) => import("nixle").Provider; 7 | -------------------------------------------------------------------------------- /packages/nixle/dist/utils/types.d.ts: -------------------------------------------------------------------------------- 1 | export type NixleTypeError = { 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 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 | (path: ValidPath, handler: RouteHandler<{}, {}, {}, Response>): Route>; 32 | }; 33 | delete: { 34 | (path: ValidPath, options: RouteOptions): Route>; 35 | (path: ValidPath, handler: RouteHandler<{}, {}, {}, Response>): Route>; 36 | }; 37 | options: { 38 | (path: ValidPath, options: RouteOptions): Route>; 39 | (path: ValidPath, handler: RouteHandler<{}, {}, {}, Response>): Route>; 40 | }; 41 | }; 42 | export { route }; 43 | export type { Route }; 44 | -------------------------------------------------------------------------------- /packages/nixle/dist/provider/RouteHandler.d.ts: -------------------------------------------------------------------------------- 1 | import type { CookieOptions, HTTPMethod, StatusCode } from '..'; 2 | export interface ProviderRouteHandlerContext

= Record, Q extends Record = Record, B extends Record = Record> { 3 | /** 4 | * Request 5 | * 6 | * @deprecated Try to not use it, if you need some features from the request, please create an issue. 7 | * @see https://github.com/letstri/nixle/issues 8 | */ 9 | request: any; 10 | /** 11 | * Response 12 | * 13 | * @deprecated Try to not use it, if you need some features from the response, please create an issue. 14 | * @see https://github.com/letstri/nixle/issues 15 | */ 16 | response: any; 17 | /** 18 | * HTTP method 19 | */ 20 | method: HTTPMethod; 21 | /** 22 | * Path parameters 23 | * 24 | * @example 25 | * // We have a path '/users/:id' 26 | * // And we have a request '/users/123' 27 | * 28 | * // After that we can get params: 29 | * // => { id: '123' } 30 | */ 31 | params: Awaited

; 32 | /** 33 | * Query parameters 34 | * 35 | * @example 36 | * // We have a request with query ?name=John 37 | * 38 | * // After that we can get query: 39 | * // => { name: 'John' } 40 | */ 41 | query: Awaited; 42 | /** 43 | * Body parameters 44 | * 45 | * @example 46 | * // We have a request with body { name: 'John' } 47 | * 48 | * // After that we can get body: 49 | * // => { name: 'John' } 50 | */ 51 | body: Awaited; 52 | /** 53 | * Set status code 54 | * 55 | * @param code 56 | * @default 200 57 | * 58 | * @example 59 | * setStatusCode(StatusCodes.BAD_REQUEST); 60 | */ 61 | setStatusCode: (code: StatusCode) => void; 62 | /** 63 | * Headers 64 | * 65 | * @readonly 66 | */ 67 | headers: Readonly>; 68 | /** 69 | * Get header 70 | * 71 | * @param key 72 | * 73 | * @example 74 | * getHeader('Content-Type'); // -> application/json 75 | */ 76 | getHeader: (key: string) => string | null; 77 | /** 78 | * Set header 79 | * 80 | * @param key 81 | * @param value 82 | * 83 | * @example 84 | * setHeader('Content-Type', 'application/json'); 85 | */ 86 | setHeader: (key: string, value: string) => void; 87 | /** 88 | * Set cookie 89 | * 90 | * @param key 91 | * @param value 92 | * @param options 93 | * 94 | * @example 95 | * setCookie('token', '123'); 96 | * setCookie('token', '123', { httpOnly: true }); 97 | */ 98 | setCookie: (key: string, value: string, options?: CookieOptions) => void; 99 | /** 100 | * Get cookie 101 | * 102 | * @param key 103 | * 104 | * @example 105 | * getCookie('token'); // -> 123 106 | */ 107 | getCookie: (key: string) => string | null; 108 | /** 109 | * Redirect 110 | * 111 | * @param url 112 | * @param statusCode 113 | * 114 | * @example 115 | * redirect('/users', StatusCode.PERMANENT_REDIRECT); 116 | */ 117 | redirect: (url: string, statusCode?: StatusCode.MOVED_PERMANENTLY | StatusCode.MOVED_TEMPORARILY | StatusCode.PERMANENT_REDIRECT | StatusCode.TEMPORARY_REDIRECT) => void | Promise; 118 | } 119 | export interface ProviderRouteHandler

= Record, Q extends Record = Record, B extends Record = Record, R extends unknown = unknown> { 120 | (context: ProviderRouteHandlerContext): R; 121 | } 122 | -------------------------------------------------------------------------------- /packages/nixle/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Nixle logo 4 | 5 |

6 |

7 | Universal server-side framework.
Backend for everyone and everywhere. 8 |

9 |

10 | 11 | 12 |

13 | 14 | ## Overview 15 | 16 | Nixle is a framework for building HTTP servers. It is designed to be simple, fast, and extensible. It is built on top of existing frameworks, such as Express, Fastify, and Nitro. 17 | 18 | - ✨ Simple and intuitive API. 19 | - 🚀 Supports multiple providers such as Express, Fastify, and Hono. 20 | - 🌐 Supports SSR frameworks such as Nuxt. 21 | - 💪 Incredible TypeScript support. 22 | - 🎯 Easy to use and extend. 23 | 24 | ## Documentation 25 | 26 | You can find the documentation [on the website](https://nixle.letstri.dev). 27 | 28 | ## Installation 29 | 30 | ```bash 31 | npm install nixle 32 | ``` 33 | 34 | ## Usage 35 | 36 | To set up your app, use the `createApp` function. Create a router with the `createRouter` function. Additionally, you can create services with module-specific logic using the `createService` function. 37 | 38 | ```ts 39 | // usersRouter.ts 40 | import { createRouter, createService } from 'nixle'; 41 | 42 | declare global { 43 | namespace Nixle { 44 | interface Env { 45 | USERS_SERVICE: string; 46 | } 47 | } 48 | } 49 | 50 | const usersService = createService('users', ({ log, env, ofetch }) => { 51 | const getUsers = async (limit: number) => { 52 | log.info('Getting users...'); 53 | 54 | const users = await ofetch<{ name: string; email: string }[]>(`${env.USERS_SERVICE}/users`); 55 | 56 | log.success(`Got ${users.length} users`); 57 | 58 | return users; 59 | }; 60 | 61 | return { 62 | getUsers, 63 | }; 64 | }); 65 | 66 | export const usersRouter = createRouter('/users', ({ route, zodObject }) => [ 67 | route.get('/', { 68 | queryValidation: zodObject({ 69 | limit: zod.number().default(10), 70 | }).validate, 71 | handler: ({ query }) => { 72 | return usersService().getUsers(query.limit); 73 | }, 74 | }), 75 | ]); 76 | ``` 77 | 78 | ## Providers 79 | 80 | We have several providers that you can use to create your app such as Nuxt, Express, Fastify, and Elysia (Bun). Choose the one that suits you best and install packages for it. More information about providers can be found in the [docs](https://nixle.letstri.dev/providers/what.html). 81 | 82 | For example, if you want to use Fastify, install the `@nixle/fastify` package besides the `nixle` package: 83 | 84 | ```bash 85 | npm install @nixle/fastify 86 | ``` 87 | 88 | Then, import the `fastifyProvider` function and pass it to the `createApp` function: 89 | 90 | ```ts 91 | import fastify from 'fastify'; 92 | import { createApp } from 'nixle'; 93 | import { fastifyProvider } from '@nixle/fastify'; 94 | import { zodPlugin } from '@nixle/zod'; 95 | import { ofetchPlugin } from '@nixle/ofetch'; 96 | import { usersRouter } from './usersRouter'; 97 | 98 | const { app, $inferRouters } = createApp({ 99 | provider: fastifyProvider(fastify()), 100 | router: [usersRouter], 101 | plugins: [zodPlugin, ofetchPlugin()], 102 | }); 103 | 104 | app.listen({ port: 4000 }); 105 | 106 | type NixleRouters = typeof $inferRouters; 107 | // { 108 | // '/users': { 109 | // '/': { 110 | // GET: { 111 | // query: { 112 | // limit: number; 113 | // }; 114 | // response: { name: string; email: string }[] 115 | // } 116 | // } 117 | // }; 118 | // } 119 | ``` 120 | 121 | ## Author 122 | 123 | © [letstri](https://letstri.dev), released under the [MIT](https://github.com/letstri/nixle/blob/main/LICENSE) license. 124 | -------------------------------------------------------------------------------- /docs/introduction/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Getting Started 6 | 7 | Nixle is a framework for building HTTP servers. It is designed to be simple, fast, and extensible. It is built on top of existing frameworks, such as Express, Fastify, and Nuxt. 8 | 9 | ## Installation 10 | 11 | You can install the `nixle` package using npm, pnpm, yarn, or bun: 12 | 13 | ::: code-group 14 | 15 | ```sh [npm] 16 | npm i nixle 17 | ``` 18 | 19 | ```sh [pnpm] 20 | pnpm add nixle 21 | ``` 22 | 23 | ```sh [yarn] 24 | yarn add nixle 25 | ``` 26 | 27 | ```sh [bun] 28 | bun i nixle 29 | ``` 30 | 31 | ::: 32 | 33 | ## Quick Start 34 | 35 | ::: tip Compatibility Note 36 | Nixle requires [Node.js](https://nodejs.org/en/) version 18+. 20+. 37 | ::: 38 | 39 | ### Create base 40 | 41 | Before you can create a server, you need to create a base. A base is a collection routes that form the foundation of your server. Once you have created a base, you can proceed to create [services](/overview/services) or explore other topics in the [Overview](/overview/app) section. 42 | 43 | 44 | ```ts 45 | // usersRouter.ts 46 | import { createRouter } from 'nixle'; 47 | 48 | export const usersRouter = createRouter('/users', ({ route }) => [ 49 | route.get('/', () => 'Hello World!'), 50 | ]); 51 | ``` 52 | 53 | 54 | ### Choose a provider 55 | 56 | Each provider provides the same functionality, but with different frameworks. You can choose from the following providers: 57 | 58 | ::: code-group 59 | 60 | ```ts [Nuxt] 61 | import { createApp } from 'nixle'; 62 | import { nitroProvider } from '@nixle/nitro'; 63 | import { usersRouter } from './usersRouter'; 64 | 65 | export default defineNitroPlugin((nitroApp) => { 66 | createApp({ 67 | provider: nitroProvider(nitroApp), 68 | routers: [usersRouter], 69 | }); 70 | }); 71 | ``` 72 | 73 | ```ts [Express] 74 | import express from 'express'; 75 | import { createApp } from 'nixle'; 76 | import { expressProvider } from '@nixle/express'; 77 | import { usersRouter } from './usersRouter'; 78 | 79 | const { app } = createApp({ 80 | provider: expressProvider(express()), 81 | routers: [usersRouter], 82 | }); 83 | 84 | app.listen(3000); 85 | ``` 86 | 87 | ```ts [Fastify] 88 | import fastify from 'fastify'; 89 | import { createApp } from 'nixle'; 90 | import { fastifyProvider } from '@nixle/fastify'; 91 | import { usersRouter } from './usersRouter'; 92 | 93 | const { app } = createApp({ 94 | provider: fastifyProvider(app), 95 | routers: [usersRouter], 96 | }); 97 | 98 | app.listen({ port: 3000 }); 99 | ``` 100 | 101 | ```ts [Elysia] 102 | import { Elysia } from 'elysia'; 103 | import { createApp } from 'nixle'; 104 | import { elysiaProvider } from '@nixle/elysia'; 105 | import { usersRouter } from './usersRouter'; 106 | 107 | const { app } = createApp({ 108 | provider: honoProvider(new Elysia()), 109 | routers: [usersRouter], 110 | }); 111 | 112 | app.listen(3000); 113 | ``` 114 | 115 | ```ts [Hono] 116 | import { Hono } from 'hono'; 117 | import { serve } from '@hono/node-server'; 118 | import { createApp } from 'nixle'; 119 | import { honoProvider } from '@nixle/hono'; 120 | import { usersRouter } from './usersRouter'; 121 | 122 | const { app } = createApp({ 123 | provider: honoProvider(app), 124 | routers: [usersRouter], 125 | }); 126 | 127 | serve(app, (info) => { 128 | console.log(`Listening on http://localhost:${info.port}`); 129 | }); 130 | ``` 131 | 132 | ::: 133 | 134 | Or [create](/providers/custom) your own provider. 135 | 136 | ### Try it 137 | 138 | After successfully creating the server, you can try it out to send a request to the server. 139 | 140 | ```ts 141 | const data = fetch('http://localhost:3000/users').then((res) => res.text()); 142 | 143 | console.log(data); // Hello World! 144 | ``` 145 | 146 | ## Next Steps 147 | 148 | Take a look at the [Overview](/overview/app) section to learn more about available features. To create clean and maintainable code you can use our services, which are described in the [Services](/overview/services) section. Also we have [logger](/overview/logger) and [plugins](/plugins/custom). 149 | -------------------------------------------------------------------------------- /packages/zod/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import { type ErrorOptions } from 'nixle'; 2 | import { z } from 'zod'; 3 | interface ZodObject { 4 | (shape: T | z.ZodObject | ((zod: typeof z) => T | z.ZodObject | z.ZodEffects> | z.ZodEffects>> | z.ZodEffects>>> | z.ZodEffects>>>> | z.ZodEffects>>>>> | z.ZodEffects>>>>>> | z.ZodEffects>>>>>>>), options?: Partial): { 7 | /** 8 | * @returns {Promise} Returns a promise with validated object 9 | * @throws {NixleError} Throws a Nixle error if validation fails 10 | */ 11 | validate(data: any): Promise>>; 12 | /** 13 | * @returns {Promise} Returns a promise with validated object 14 | * @throws {NixleError} Throws a Nixle error if validation fails 15 | */ 16 | validatePartial(data: any): Promise; 18 | }>>>; 19 | /** 20 | * @example 21 | * 22 | * const { validate, $infer } = zodObject({ 23 | * email: z.string().email(), 24 | * password: z.string().min(8), 25 | * }); 26 | * 27 | * type User = typeof $infer; 28 | */ 29 | $infer: z.infer>; 30 | /** 31 | * @example 32 | * 33 | * const { validate, $inferPartial } = zodObject({ 34 | * email: z.string().email(), 35 | * password: z.string().min(8), 36 | * }); 37 | * 38 | * type User = typeof $inferPartial; 39 | */ 40 | $inferPartial: z.infer; 42 | }>>; 43 | }; 44 | } 45 | declare global { 46 | namespace Nixle { 47 | interface ServiceContext { 48 | zodObject: ZodObject; 49 | } 50 | interface RouterContext { 51 | zodObject: ZodObject; 52 | } 53 | } 54 | } 55 | /** 56 | * @param shape 57 | * 58 | * @example 59 | * const usersRouter = createRouter('/users', ({ route, zodObject }) => [ 60 | * route.get('/', { 61 | * bodyValidation: zodObject((zod) => ({ 62 | * email: zod.string().email(), 63 | * password: zod.string().min(8), 64 | * })).validate, 65 | * handler: ({ body }) => `Hello ${body.email}!`, 66 | * }), 67 | * ]); 68 | * 69 | * @example 70 | * import { zodObject } from '@nixle/zod'; 71 | * 72 | * const { validate } = zodObject((z) => ({ 73 | * email: z.string().email(), 74 | * password: z.string().min(8), 75 | * })) 76 | * 77 | * @example 78 | * import { zodObject } from '@nixle/zod'; 79 | * import { z } from 'zod'; 80 | * 81 | * const { validate } = zodObject({ 82 | * email: z.string().email(), 83 | * password: z.string().min(8), 84 | * }); 85 | * 86 | * @example 87 | * import { zodObject } from '@nixle/zod'; 88 | * 89 | * const { validate } = zodObject((z) => 90 | * z 91 | * .object({ 92 | * password: z.string().min(8), 93 | * oldPassword: z.string().min(8), 94 | * }) 95 | * .refine((obj) => obj.password !== obj.oldPassword), 96 | * ); 97 | * 98 | * @example 99 | * 100 | * import { zodObject } from '@nixle/zod'; 101 | * 102 | * const { validatePartial } = zodObject({ 103 | * email: z.string().email(), 104 | * password: z.string().min(8), 105 | * }); 106 | * 107 | * @param options 108 | * 109 | * @example 110 | * const { validate } = zodObject((z) => ({ 111 | * email: z.string().email(), 112 | * password: z.string().min(8), 113 | * }), { 114 | * message: 'Custom message', 115 | * statusCode: StatusCode.BAD_REQUEST, 116 | * }); 117 | */ 118 | export declare const zodObject: ZodObject; 119 | export declare const zodPlugin: () => import("nixle").Plugin; 120 | export {}; 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | 3 | I created Nixle due to the lack of frameworks in Node.js that have the ability to use types in the frontend that were generated in the backend. However, that was because I had never used tRPC. That's why I'm now closing Nixle and have switched to tRPC in my production project. 4 | 5 | --- 6 | 7 |

8 | 9 | Nixle logo 10 | 11 |

12 |

13 | Universal server-side framework.
Backend for everyone and everywhere. 14 |

15 |

16 | 17 | 18 |

19 | 20 | ## Overview 21 | 22 | Nixle is a framework for building HTTP servers. It is designed to be simple, fast, and extensible. It is built on top of existing frameworks, such as Express, Fastify, and Nuxt. 23 | 24 | - ✨ Simple and intuitive API. 25 | - 🚀 Supports multiple providers such as Express, Fastify, and Hono. 26 | - 🌐 Supports SSR frameworks such as Nuxt. 27 | - 💪 Incredible TypeScript support. 28 | - 🎯 Easy to use and extend. 29 | 30 | ## Documentation 31 | 32 | You can find the documentation [on the website](https://nixle.letstri.dev). 33 | 34 | ## Installation 35 | 36 | ```bash 37 | npm install nixle 38 | ``` 39 | 40 | ## Usage 41 | 42 | To set up your app, use the `createApp` function. Create a router with the `createRouter` function. Additionally, you can create services with module-specific logic using the `createService` function. 43 | 44 | ```ts 45 | // usersRouter.ts 46 | import { createRouter, createService } from 'nixle'; 47 | 48 | declare global { 49 | namespace Nixle { 50 | interface Env { 51 | USERS_SERVICE: string; 52 | } 53 | } 54 | } 55 | 56 | const usersService = createService('users', ({ log, env, ofetch }) => { 57 | const getUsers = async (limit: number) => { 58 | log.info('Getting users...'); 59 | 60 | const users = await ofetch<{ name: string; email: string }[]>(`${env.USERS_SERVICE}/users`); 61 | 62 | log.success(`Got ${users.length} users`); 63 | 64 | return users; 65 | }; 66 | 67 | return { 68 | getUsers, 69 | }; 70 | }); 71 | 72 | export const usersRouter = createRouter('/users', ({ route, zodObject }) => [ 73 | route.get('/', { 74 | queryValidation: zodObject({ 75 | limit: zod.number().default(10), 76 | }).validate, 77 | handler: ({ query }) => { 78 | return usersService().getUsers(query.limit); 79 | }, 80 | }), 81 | ]); 82 | ``` 83 | 84 | ## Providers 85 | 86 | We have several providers that you can use to create your app such as Nuxt, Express, Fastify, and Elysia (Bun). Choose the one that suits you best and install packages for it. More information about providers can be found in the [docs](https://nixle.letstri.dev/providers/what.html). 87 | 88 | For example, if you want to use Fastify, install the `@nixle/fastify` package besides the `nixle` package: 89 | 90 | ```bash 91 | npm install @nixle/fastify 92 | ``` 93 | 94 | Then, import the `fastifyProvider` function and pass it to the `createApp` function: 95 | 96 | ```ts 97 | import fastify from 'fastify'; 98 | import { createApp } from 'nixle'; 99 | import { fastifyProvider } from '@nixle/fastify'; 100 | import { zodPlugin } from '@nixle/zod'; 101 | import { ofetchPlugin } from '@nixle/ofetch'; 102 | import { usersRouter } from './usersRouter'; 103 | 104 | const { app, $inferRouters } = createApp({ 105 | provider: fastifyProvider(fastify()), 106 | router: [usersRouter], 107 | plugins: [zodPlugin, ofetchPlugin()], 108 | }); 109 | 110 | app.listen({ port: 4000 }); 111 | 112 | type NixleRouters = typeof $inferRouters; 113 | // { 114 | // '/users': { 115 | // '/': { 116 | // GET: { 117 | // query: { 118 | // limit: number; 119 | // }; 120 | // response: { name: string; email: string }[] 121 | // } 122 | // } 123 | // }; 124 | // } 125 | ``` 126 | 127 | ## Author 128 | 129 | © [letstri](https://letstri.dev), released under the [MIT](https://github.com/letstri/nixle/blob/main/LICENSE) license. 130 | -------------------------------------------------------------------------------- /packages/nixle/src/createError.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | import { colorize } from 'consola/utils'; 3 | import { log } from './logger'; 4 | import { isPrimitive, exclude } from './utils/helpers'; 5 | import { hooks } from './hooks'; 6 | import { StatusCode } from '.'; 7 | 8 | export interface ErrorOptions { 9 | /** 10 | * @example 'User with id 1 not found' 11 | */ 12 | message: string; 13 | /** 14 | * @default 400 // Bad Request 15 | * 16 | * @example StatusCode.BAD_REQUEST 17 | */ 18 | statusCode?: number; 19 | /** 20 | * @example user_not_found 21 | */ 22 | code?: string | number; 23 | /** 24 | * Should be an object 25 | * 26 | * @example { id: 1 } 27 | */ 28 | details?: D; 29 | } 30 | 31 | export class NixleError extends Error { 32 | constructor({ statusCode, message, details, code }: ErrorOptions) { 33 | super(); 34 | Error.captureStackTrace(this, this.constructor); 35 | this.name = 'NixleError'; 36 | this.statusCode = statusCode || StatusCode.BAD_REQUEST; 37 | this.message = message; 38 | this.details = details; 39 | this.code = code; 40 | } 41 | 42 | time = dayjs().format(); 43 | statusCode: StatusCode; 44 | message = 'Internal Server Error'; 45 | details?: D; 46 | code?: string | number; 47 | } 48 | 49 | const formatErrorStack = (stack: string) => { 50 | const colorizeLine = (str: string) => { 51 | let newLine = str; 52 | const file = str.match(/\((.*?)\)/g)?.[0].slice(1, -1); 53 | 54 | if (file) { 55 | newLine = newLine.replace(file, colorize('underline', file)); 56 | } 57 | 58 | return colorize('dim', colorize('redBright', newLine)); 59 | }; 60 | 61 | return `\n${stack.split('\n').slice(1).map(colorizeLine).join('\n')}`; 62 | }; 63 | 64 | export function createError(options: ErrorOptions): NixleError; 65 | export function createError( 66 | message: string, 67 | statusCode?: StatusCode, 68 | ): NixleError; 69 | 70 | export function createError( 71 | optionsOrMessage: string | ErrorOptions, 72 | statusCode?: StatusCode, 73 | ): NixleError { 74 | const message = 75 | typeof optionsOrMessage === 'string' ? optionsOrMessage : optionsOrMessage.message; 76 | 77 | return new NixleError({ 78 | message, 79 | statusCode: 80 | typeof optionsOrMessage === 'string' 81 | ? statusCode || StatusCode.BAD_REQUEST 82 | : optionsOrMessage.statusCode || StatusCode.BAD_REQUEST, 83 | code: typeof optionsOrMessage === 'string' ? undefined : optionsOrMessage.code, 84 | details: 85 | typeof optionsOrMessage === 'string' ? ({} as D) : optionsOrMessage.details || ({} as D), 86 | }); 87 | } 88 | 89 | export const isNixleError = (error: any): error is NixleError => { 90 | return error instanceof NixleError; 91 | }; 92 | 93 | export const logError = async (error: any, _log: typeof log) => { 94 | let message = ''; 95 | 96 | if (isNixleError(error) || error instanceof Error) { 97 | message = error.message; 98 | } else if (isPrimitive(error)) { 99 | message = error; 100 | } else { 101 | message = `${error.constructor.name} ${JSON.stringify(error)}`; 102 | } 103 | 104 | const _details = JSON.stringify((error as NixleError)?.details, null, 2); 105 | const details = !!_details && Object.keys(_details).length && _details !== '{}' && _details; 106 | const params = [colorize('red', message), details && colorize('red', details)]; 107 | 108 | if (error && (!error.statusCode || error.statusCode >= StatusCode.INTERNAL_SERVER_ERROR)) { 109 | if (error instanceof Error) { 110 | const { stack } = error; 111 | 112 | if (stack) { 113 | params.push('\n'); 114 | params.push(formatErrorStack(stack)); 115 | } 116 | } 117 | 118 | _log.fatal(...params.filter(Boolean)); 119 | } else { 120 | _log.error(...params.filter(Boolean)); 121 | } 122 | 123 | await hooks.callHook('error', error); 124 | }; 125 | 126 | export const transformErrorToResponse = (error: any, statusCode: StatusCode) => { 127 | const defaultTime = dayjs().format(); 128 | const isPrimitiveError = isPrimitive(error); 129 | 130 | const json: Omit, 'name'> = { 131 | statusCode, 132 | message: (isPrimitiveError && error) || error.message || 'Internal Server Error', 133 | time: (isPrimitiveError && defaultTime) || error.time || defaultTime, 134 | details: (isPrimitiveError && {}) || error.details || {}, 135 | code: (isPrimitiveError && undefined) || error.code, 136 | }; 137 | 138 | const _error = JSON.parse(JSON.stringify(error, Object.getOwnPropertyNames(error))); 139 | 140 | if ( 141 | json.details !== null && 142 | typeof json.details === 'object' && 143 | Object.keys(json.details).length === 0 144 | ) { 145 | json.details = { 146 | ...json.details, 147 | ...exclude(isPrimitive(_error) ? {} : _error, [ 148 | 'message', 149 | 'name', 150 | 'stack', 151 | 'statusCode', 152 | 'time', 153 | 'details', 154 | 'code', 155 | ]), 156 | }; 157 | } 158 | 159 | return json; 160 | }; 161 | -------------------------------------------------------------------------------- /packages/nixle/src/router/Route.ts: -------------------------------------------------------------------------------- 1 | import type { StatusCode } from '~/index'; 2 | import type { Guard } from '~/createGuard'; 3 | import type { Middleware } from '~/createMiddleware'; 4 | import type { ProviderRouteHandlerContext } from '~/provider/RouteHandler'; 5 | 6 | export interface RouteHandlerContext< 7 | P extends Record = Record, 8 | Q extends Record = Record, 9 | B extends Record = Record, 10 | > extends ProviderRouteHandlerContext { 11 | /** 12 | * Custom data that you can pass to the context. 13 | * 14 | * `setData` with one param will merge the data with the existing one. 15 | * `setData` with two params will set the value for the key. 16 | * 17 | * `getData` with no params will return all data. 18 | * `getData` with one param will return the value for the key. 19 | * 20 | * @example 21 | * const userIdMiddleware = createMiddleware('user-id', ({ setData, getHeader }) => { 22 | * const token = getHeader('token'); 23 | * 24 | * setData('userId', parseToken(token).userId); // Some logic to parse token 25 | * 26 | * if (!data.userId) { 27 | * throw createError('User ID is required', StatusCodes.UNAUTHORIZED); 28 | * } 29 | * }); 30 | * 31 | * const profileServices = createService('profile', ({ log }) => { 32 | * const getVideos = async (id) => { 33 | * log.info(`Getting videos for user ${id}`); 34 | * return []; 35 | * }; 36 | * 37 | * return { getVideos }; 38 | * }); 39 | * 40 | * const profileRouter = createRouter('/profile', { 41 | * middlewares: [userIdMiddleware], 42 | * routes: ({ route }) => [ 43 | * route.get('/profile/videos', ({ getData }) => profileServices().getVideos(getData('userId'))), 44 | * ], 45 | * }); 46 | */ 47 | setData>(data: D): void; 48 | setData(key: K, value: V): void; 49 | getData>(): T; 50 | getData, K extends keyof T = keyof T>(key: K): T[K]; 51 | /** 52 | * Environment variables. 53 | */ 54 | env: Nixle.Env & { 55 | get(key: K): Nixle.Env[K] | undefined; 56 | getOrThrow(key: K): Nixle.Env[K]; 57 | }; 58 | } 59 | 60 | export interface RouteHandler

{ 61 | (context: RouteHandlerContext): R; 62 | } 63 | 64 | export interface RouteOptions

{ 65 | /** 66 | * Status code 67 | * @default 200 68 | */ 69 | statusCode?: StatusCode; 70 | /** 71 | * Path params validation. 72 | * In the method you can validate incoming params. 73 | * 74 | * @param params Incoming params 75 | * 76 | * @example 77 | * paramsValidation(params) { 78 | * // We have a path '/users/:id' 79 | * if (!params.id || typeof params.id !== 'string') { 80 | * throw createError('ID is required'); 81 | * } 82 | * 83 | * return { id: params.id }; 84 | * } 85 | */ 86 | paramsValidation?(params: any): P; 87 | /** 88 | * Body validation. 89 | * In the method you can validate body. 90 | * 91 | * @param body Incoming body 92 | * 93 | * @example 94 | * bodyValidation(body) { 95 | * if (!body.name || typeof body.name !== 'string') { 96 | * throw createError('Name is required'); 97 | * } 98 | * 99 | * return { name: body.name }; 100 | * } 101 | */ 102 | bodyValidation?(body: any): B; 103 | /** 104 | * Query handler. 105 | * In the method you can validate query. 106 | * 107 | * @param query Incoming query 108 | * 109 | * @example 110 | * queryValidation(query) { 111 | * if (!query.name || typeof query.name !== 'string') { 112 | * throw createError('Name is required'); 113 | * } 114 | * 115 | * return { name: query.name }; 116 | * } 117 | */ 118 | queryValidation?(query: any): Q; 119 | /** 120 | * Middleware handler. 121 | * In the method you can do anything you want and it will be called before the main handler. 122 | * If you return something the main handler will not be called. 123 | * 124 | * @param context All available context methods and properties 125 | * 126 | * @example 127 | * middlewares: [ 128 | * createMiddleware('log', { log }) { 129 | * log('Hello world!'); 130 | * }), 131 | * ] 132 | */ 133 | middlewares?: Middleware[]; 134 | /** 135 | * Main request handler. 136 | * In the method you can do anything you want but we recommend to call a service created with `createService`. 137 | * Returned value will be sent to the client. 138 | * 139 | * @param context All available context methods and properties 140 | * 141 | * @example 142 | * handler(context) { 143 | * return { message: 'Hello world!' }; 144 | * } 145 | */ 146 | handler: RouteHandler; 147 | /** 148 | * Guards. 149 | * 150 | * @example 151 | * guards: [ 152 | * createGuard(async ({ getCookie }) => { 153 | * if (!getCookie('token')) { 154 | * throw createError('Token is required', StatusCodes.UNAUTHORIZED); 155 | * } 156 | * }), 157 | * ] 158 | */ 159 | guards?: Guard[]; 160 | } 161 | -------------------------------------------------------------------------------- /packages/nixle/dist/router/Route.d.ts: -------------------------------------------------------------------------------- 1 | import type { StatusCode } from '../index'; 2 | import type { Guard } from '../createGuard'; 3 | import type { Middleware } from '../createMiddleware'; 4 | import type { ProviderRouteHandlerContext } from '../provider/RouteHandler'; 5 | export interface RouteHandlerContext

= Record, Q extends Record = Record, B extends Record = Record> extends ProviderRouteHandlerContext { 6 | /** 7 | * Custom data that you can pass to the context. 8 | * 9 | * `setData` with one param will merge the data with the existing one. 10 | * `setData` with two params will set the value for the key. 11 | * 12 | * `getData` with no params will return all data. 13 | * `getData` with one param will return the value for the key. 14 | * 15 | * @example 16 | * const userIdMiddleware = createMiddleware('user-id', ({ setData, getHeader }) => { 17 | * const token = getHeader('token'); 18 | * 19 | * setData('userId', parseToken(token).userId); // Some logic to parse token 20 | * 21 | * if (!data.userId) { 22 | * throw createError('User ID is required', StatusCodes.UNAUTHORIZED); 23 | * } 24 | * }); 25 | * 26 | * const profileServices = createService('profile', ({ log }) => { 27 | * const getVideos = async (id) => { 28 | * log.info(`Getting videos for user ${id}`); 29 | * return []; 30 | * }; 31 | * 32 | * return { getVideos }; 33 | * }); 34 | * 35 | * const profileRouter = createRouter('/profile', { 36 | * middlewares: [userIdMiddleware], 37 | * routes: ({ route }) => [ 38 | * route.get('/profile/videos', ({ getData }) => profileServices().getVideos(getData('userId'))), 39 | * ], 40 | * }); 41 | */ 42 | setData>(data: D): void; 43 | setData(key: K, value: V): void; 44 | getData>(): T; 45 | getData, K extends keyof T = keyof T>(key: K): T[K]; 46 | /** 47 | * Environment variables. 48 | */ 49 | env: Nixle.Env & { 50 | get(key: K): Nixle.Env[K] | undefined; 51 | getOrThrow(key: K): Nixle.Env[K]; 52 | }; 53 | } 54 | export interface RouteHandler

{ 55 | (context: RouteHandlerContext): R; 56 | } 57 | export interface RouteOptions

{ 58 | /** 59 | * Status code 60 | * @default 200 61 | */ 62 | statusCode?: StatusCode; 63 | /** 64 | * Path params validation. 65 | * In the method you can validate incoming params. 66 | * 67 | * @param params Incoming params 68 | * 69 | * @example 70 | * paramsValidation(params) { 71 | * // We have a path '/users/:id' 72 | * if (!params.id || typeof params.id !== 'string') { 73 | * throw createError('ID is required'); 74 | * } 75 | * 76 | * return { id: params.id }; 77 | * } 78 | */ 79 | paramsValidation?(params: any): P; 80 | /** 81 | * Body validation. 82 | * In the method you can validate body. 83 | * 84 | * @param body Incoming body 85 | * 86 | * @example 87 | * bodyValidation(body) { 88 | * if (!body.name || typeof body.name !== 'string') { 89 | * throw createError('Name is required'); 90 | * } 91 | * 92 | * return { name: body.name }; 93 | * } 94 | */ 95 | bodyValidation?(body: any): B; 96 | /** 97 | * Query handler. 98 | * In the method you can validate query. 99 | * 100 | * @param query Incoming query 101 | * 102 | * @example 103 | * queryValidation(query) { 104 | * if (!query.name || typeof query.name !== 'string') { 105 | * throw createError('Name is required'); 106 | * } 107 | * 108 | * return { name: query.name }; 109 | * } 110 | */ 111 | queryValidation?(query: any): Q; 112 | /** 113 | * Middleware handler. 114 | * In the method you can do anything you want and it will be called before the main handler. 115 | * If you return something the main handler will not be called. 116 | * 117 | * @param context All available context methods and properties 118 | * 119 | * @example 120 | * middlewares: [ 121 | * createMiddleware('log', { log }) { 122 | * log('Hello world!'); 123 | * }), 124 | * ] 125 | */ 126 | middlewares?: Middleware[]; 127 | /** 128 | * Main request handler. 129 | * In the method you can do anything you want but we recommend to call a service created with `createService`. 130 | * Returned value will be sent to the client. 131 | * 132 | * @param context All available context methods and properties 133 | * 134 | * @example 135 | * handler(context) { 136 | * return { message: 'Hello world!' }; 137 | * } 138 | */ 139 | handler: RouteHandler; 140 | /** 141 | * Guards. 142 | * 143 | * @example 144 | * guards: [ 145 | * createGuard(async ({ getCookie }) => { 146 | * if (!getCookie('token')) { 147 | * throw createError('Token is required', StatusCodes.UNAUTHORIZED); 148 | * } 149 | * }), 150 | * ] 151 | */ 152 | guards?: Guard[]; 153 | } 154 | -------------------------------------------------------------------------------- /packages/nixle/src/router/buildRouter.ts: -------------------------------------------------------------------------------- 1 | import { colors } from 'consola/utils'; 2 | import type { HTTPMethod } from '~/types/HTTPMethod'; 3 | import { contextLog } from '~/logger'; 4 | import type { AppOptions } from '~/createApp'; 5 | import { getEnv } from '~/env'; 6 | import { createError, logError, transformErrorToResponse, type NixleError } from '~/createError'; 7 | import { hooks } from '~/hooks'; 8 | import { StatusCode, type Router, type RouteHandlerContext } from '..'; 9 | import { joinPath, parseObject } from '~/utils/helpers'; 10 | 11 | export const buildRouter = (appOptions: AppOptions, router: Router) => { 12 | const routerPath = joinPath(appOptions.globalPrefix || '', router.path || ''); 13 | const routerLog = contextLog(routerPath, 'bgGreen'); 14 | const routes = router.routes(); 15 | 16 | try { 17 | if (routes.length === 0) { 18 | throw createError('At least one router is required', StatusCode.INTERNAL_SERVER_ERROR); 19 | } 20 | 21 | if (routes.some(({ path, method, options }) => !path || !method || !options.handler)) { 22 | throw createError( 23 | 'Path, method and handler are required for each route', 24 | StatusCode.INTERNAL_SERVER_ERROR, 25 | ); 26 | } 27 | } catch (e) { 28 | logError(e, routerLog); 29 | process.exit(1); 30 | } 31 | 32 | routes.forEach(function buildRouters({ path, method, options }) { 33 | const routePath = joinPath(routerPath, path); 34 | const log = contextLog(`${colors.bold(method)} ${routePath}`, 'bgGreen'); 35 | 36 | appOptions.provider.createRoute({ 37 | method: method.toLowerCase() as Lowercase, 38 | path: routePath, 39 | async handler(context) { 40 | const data: any = {}; 41 | 42 | const getData = , K extends keyof T>( 43 | key?: K, 44 | ): K extends undefined ? T : T[K] => (key ? data[key] || null : data); 45 | const setData = (keyOrData: T, value?: V) => { 46 | if (typeof keyOrData === 'string') { 47 | data[keyOrData] = value; 48 | } else { 49 | Object.assign(data, keyOrData); 50 | } 51 | }; 52 | 53 | const _context: RouteHandlerContext = { 54 | ...context, 55 | query: parseObject(context.query), 56 | params: parseObject(context.params), 57 | headers: Object.fromEntries( 58 | Object.entries(context.headers) 59 | .filter(([, value]) => typeof value === 'string') 60 | .map(([key, value]) => [key.toLowerCase(), value]), 61 | ), 62 | env: getEnv(), 63 | getData, 64 | setData, 65 | }; 66 | 67 | await hooks.callHook('request', context); 68 | 69 | try { 70 | if (appOptions?.middlewares?.length) { 71 | await Promise.all( 72 | appOptions.middlewares.map(function executeAppMiddleware(middleware) { 73 | return middleware(_context); 74 | }), 75 | ); 76 | } 77 | if (router?.middlewares?.length) { 78 | await Promise.all( 79 | router.middlewares.map(function executeRouterMiddleware(middleware) { 80 | return middleware(_context); 81 | }), 82 | ); 83 | } 84 | if (options?.middlewares?.length) { 85 | await Promise.all( 86 | options.middlewares.map(function executeRouteMiddleware(middleware) { 87 | return middleware(_context); 88 | }), 89 | ); 90 | } 91 | } catch (error) { 92 | await logError(error, log); 93 | const statusCode = (error as NixleError)?.statusCode || StatusCode.INTERNAL_SERVER_ERROR; 94 | context.setStatusCode(statusCode); 95 | return transformErrorToResponse(error, statusCode); 96 | } 97 | 98 | try { 99 | if (router.guards.length) { 100 | await Promise.all( 101 | router.guards.map(function validateRouterGuard(guard) { 102 | return guard(_context); 103 | }), 104 | ); 105 | } 106 | if (options?.guards?.length) { 107 | await Promise.all( 108 | options.guards.map(function validateRouteGuard(guard) { 109 | return guard(_context); 110 | }), 111 | ); 112 | } 113 | 114 | const [_query, _params, _body] = await Promise.all([ 115 | options?.queryValidation?.(_context.query), 116 | options?.paramsValidation?.(_context.params), 117 | options?.bodyValidation?.(_context.body), 118 | ]); 119 | 120 | _context.query = _query || _context.query; 121 | _context.params = _params || _context.params; 122 | _context.body = _body || _context.body; 123 | } catch (error) { 124 | const statusCode = (error as NixleError)?.statusCode || StatusCode.BAD_REQUEST; 125 | context.setStatusCode(statusCode); 126 | return transformErrorToResponse(error, statusCode); 127 | } 128 | 129 | try { 130 | const response = await options.handler(_context); 131 | 132 | await hooks.callHook('response', response); 133 | 134 | if (options?.statusCode) { 135 | context.setStatusCode(options.statusCode); 136 | } 137 | 138 | return response; 139 | } catch (error) { 140 | await logError(error, log); 141 | const statusCode = (error as NixleError)?.statusCode || StatusCode.INTERNAL_SERVER_ERROR; 142 | context.setStatusCode(statusCode); 143 | return transformErrorToResponse(error, statusCode); 144 | } 145 | }, 146 | }); 147 | }); 148 | }; 149 | -------------------------------------------------------------------------------- /docs/overview/routers.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Routers 6 | 7 | Routers are a fundamental and essential part of the Nixle framework. They allow you to register routes and define the logic to handle incoming requests. 8 | 9 | ::: tip 10 | Nixle currently supports only JSON bodies. Support for other body types will be added in future updates. 11 | ::: 12 | 13 | ## Creating 14 | 15 | To create a router, you need to use the `createRouter` function. 16 | 17 | 18 | ```ts 19 | import { createRouter } from 'nixle'; 20 | 21 | export const usersRouter = createRouter('/users', ({ route }) => [ 22 | route.get('/', () => 'Hello World!'), 23 | ]); 24 | ``` 25 | 26 | ## Usage 27 | 28 | To use a router, you need to pass it to the `routers` array when creating an application. 29 | 30 | 31 | ```ts 32 | import { createApp } from 'nixle'; 33 | import { usersRouter } from './usersRouter'; 34 | 35 | createApp({ 36 | routers: [usersRouter], 37 | }) 38 | ``` 39 | 40 | 41 | 42 | Connect it to `createApp` and try to send a request to the `/users` path. 43 | 44 | ```ts 45 | fetch('http://localhost:4000/users') 46 | .then((res) => res.text()) 47 | .then(console.log); 48 | 49 | // Hello World! 50 | ``` 51 | 52 | ## Routers 53 | 54 | ### Simple router 55 | 56 | You can register simple routes by using the `route` object. The `route` object provides methods for different HTTP methods such as `get`, `post`, `delete`, etc. You can define the route path and the corresponding handler function to handle incoming requests for that route. 57 | 58 | 59 | ```ts 60 | import { createRouter } from 'nixle'; 61 | 62 | export const usersRouter = createRouter('/users', ({ route }) => [ 63 | route.get('/', () => 'Hello World!'), 64 | ]); 65 | ``` 66 | 67 | 68 | 69 | ### Routes and services 70 | 71 | You can use services in routes by using the returned function from the `createService` function. 72 | 73 | ```ts{3-9,13} 74 | import { createRouter, createService } from 'nixle'; 75 | 76 | const usersService = createService('users', () => { 77 | const getUsers = () => { 78 | return ['John', 'Jane']; 79 | }; 80 | 81 | return { getUsers }; 82 | }); 83 | 84 | export const usersRouter = createRouter('/users', ({ route }) => [ 85 | route.get('/', () => { 86 | const users = await usersService().getUsers(); 87 | 88 | return users; 89 | }), 90 | ]); 91 | ``` 92 | 93 | ## Routes 94 | 95 | ### Simple route 96 | 97 | Simple routes are the most common type of routes. They allow you to register a route and define the logic to handle incoming requests. 98 | 99 | ```ts 100 | import { createRouter } from 'nixle'; 101 | 102 | export const usersRouter = createRouter('/users', ({ route }) => [ 103 | route.get('/', () => 'Hello World!'), 104 | ]); 105 | ``` 106 | 107 | ### Route as an object 108 | 109 | ```ts 110 | import { createRouter } from 'nixle'; 111 | 112 | export const usersRouter = createRouter('/users', ({ route }) => [ 113 | route.get('/', { 114 | handler: () => 'Hello World!', 115 | }), 116 | ]); 117 | ``` 118 | 119 | ### Parameters 120 | 121 | In each route, you can use context parameters to get information about the request. 122 | 123 | Available parameters: 124 | 125 | - `params` - URL parameters (e.g. `/users/:id`) 126 | - `body` - Request body (for now supports only JSON) 127 | - `query` - Query parameters (e.g. `/users?name=John`) 128 | - `url` - Request URL 129 | - `method` - Request method 130 | - `getCookie` - Function to get cookie 131 | - `setCookie` - Function to set cookie 132 | - `getHeader` - Function to get header 133 | - `setHeader` - Function to set header 134 | - `headers` - Request headers 135 | 136 | ```ts 137 | import { createRouter } from 'nixle'; 138 | 139 | export const usersRouter = createRouter('/users', ({ route }) => [ 140 | route.get( 141 | '/', 142 | ({ params, body, query, url, method, getCookie, setCookie, getHeader, setHeader, headers }) => { 143 | return 'Hello World!'; 144 | }, 145 | ), 146 | ]); 147 | ``` 148 | 149 | ### Validation 150 | 151 | You can validate the request body by using the `validate` method. For example, you can use the [`@nixle/zod`](/plugins/zod) plugin to validate the request information. 152 | 153 | ```ts 154 | import { createRouter } from 'nixle'; 155 | 156 | export const usersRouter = createRouter('/users', ({ route, zodObject }) => [ 157 | route.post('/:id', { 158 | paramsValidation: zodObject((z) => ({ 159 | id: z.string(), 160 | })).validate, 161 | queryValidation: zodObject((z) => ({ 162 | page: z.string(), 163 | })).validate, 164 | bodyValidation: zodObject((z) => ({ 165 | name: z.string(), 166 | })).validate, 167 | handler: () => 'Hello World!', 168 | }), 169 | ]); 170 | ``` 171 | 172 | ### Middlewares 173 | 174 | You can use middlewares to execute code before the route handler. Context parameters are the same as in the route handler. 175 | 176 | ```ts 177 | import { createRouter, StatusCode, createError, createMiddleware } from 'nixle'; 178 | 179 | const auth = createMiddleware('auth', async ({ getHeader }) => { 180 | const token = getHeader('Authorization'); 181 | 182 | // Or you can use some library to verify the token 183 | 184 | if (!token) { 185 | throw createError('Unauthorized', StatusCode.UNAUTHORIZED); 186 | } 187 | }); 188 | 189 | export const usersRouter = createRouter('/users', ({ route, log }) => [ 190 | route.get('/', { 191 | middlewares: [auth], 192 | handler: () => 'Hello World!', 193 | }), 194 | ]); 195 | ``` 196 | 197 | ### Guards 198 | 199 | Guards are used to protect routes. They are executed before the route handler. If the guard returns an error, the route handler will not be executed. 200 | 201 | ```ts 202 | import { createRouter, StatusCode, createError, createGuard } from 'nixle'; 203 | 204 | const authGuard = createGuard('auth', async ({ getHeader }) => { 205 | const token = getHeader('Authorization'); 206 | 207 | // Or you can use some library to verify the token 208 | 209 | if (!token) { 210 | throw createError('Unauthorized', StatusCode.UNAUTHORIZED); 211 | } 212 | }); 213 | 214 | export const usersRouter = createRouter('/users', { 215 | guards: [authGuard], 216 | routes: ({ route, log }) => [route.get('/', () => 'Hello World!')], 217 | }); 218 | ``` 219 | 220 | Or you can use the `guards` property in the route object. 221 | 222 | ```ts 223 | export const usersRouter = createRouter('/users', ({ route, log }) => [ 224 | route.get('/', { 225 | guards: [authGuard], 226 | handler: () => 'Hello World!', 227 | }), 228 | ]); 229 | ``` 230 | -------------------------------------------------------------------------------- /packages/zod/src/index.ts: -------------------------------------------------------------------------------- 1 | import { StatusCode, createError, createPlugin, type ErrorOptions } from 'nixle'; 2 | import { z } from 'zod'; 3 | 4 | interface ZodObject { 5 | ( 6 | shape: 7 | | T 8 | | z.ZodObject 9 | | (( 10 | zod: typeof z, 11 | ) => 12 | | T 13 | | z.ZodObject 14 | | z.ZodEffects> 15 | | z.ZodEffects>> 16 | | z.ZodEffects>>> 17 | | z.ZodEffects>>>> 18 | | z.ZodEffects>>>>> 19 | | z.ZodEffects< 20 | z.ZodEffects>>>>> 21 | > 22 | | z.ZodEffects< 23 | z.ZodEffects< 24 | z.ZodEffects>>>>> 25 | > 26 | >), 27 | options?: Partial, 28 | ): { 29 | /** 30 | * @returns {Promise} Returns a promise with validated object 31 | * @throws {NixleError} Throws a Nixle error if validation fails 32 | */ 33 | validate(data: any): Promise>>; 34 | /** 35 | * @returns {Promise} Returns a promise with validated object 36 | * @throws {NixleError} Throws a Nixle error if validation fails 37 | */ 38 | validatePartial(data: any): Promise< 39 | z.infer< 40 | z.ZodObject<{ 41 | [k in keyof T]: z.ZodOptional; 42 | }> 43 | > 44 | >; 45 | /** 46 | * @example 47 | * 48 | * const { validate, $infer } = zodObject({ 49 | * email: z.string().email(), 50 | * password: z.string().min(8), 51 | * }); 52 | * 53 | * type User = typeof $infer; 54 | */ 55 | $infer: z.infer>; 56 | /** 57 | * @example 58 | * 59 | * const { validate, $inferPartial } = zodObject({ 60 | * email: z.string().email(), 61 | * password: z.string().min(8), 62 | * }); 63 | * 64 | * type User = typeof $inferPartial; 65 | */ 66 | $inferPartial: z.infer< 67 | z.ZodObject<{ 68 | [k in keyof T]: z.ZodOptional; 69 | }> 70 | >; 71 | }; 72 | } 73 | 74 | declare global { 75 | namespace Nixle { 76 | interface ServiceContext { 77 | zodObject: ZodObject; 78 | } 79 | 80 | interface RouterContext { 81 | zodObject: ZodObject; 82 | } 83 | } 84 | } 85 | 86 | /** 87 | * @param shape 88 | * 89 | * @example 90 | * const usersRouter = createRouter('/users', ({ route, zodObject }) => [ 91 | * route.get('/', { 92 | * bodyValidation: zodObject((zod) => ({ 93 | * email: zod.string().email(), 94 | * password: zod.string().min(8), 95 | * })).validate, 96 | * handler: ({ body }) => `Hello ${body.email}!`, 97 | * }), 98 | * ]); 99 | * 100 | * @example 101 | * import { zodObject } from '@nixle/zod'; 102 | * 103 | * const { validate } = zodObject((z) => ({ 104 | * email: z.string().email(), 105 | * password: z.string().min(8), 106 | * })) 107 | * 108 | * @example 109 | * import { zodObject } from '@nixle/zod'; 110 | * import { z } from 'zod'; 111 | * 112 | * const { validate } = zodObject({ 113 | * email: z.string().email(), 114 | * password: z.string().min(8), 115 | * }); 116 | * 117 | * @example 118 | * import { zodObject } from '@nixle/zod'; 119 | * 120 | * const { validate } = zodObject((z) => 121 | * z 122 | * .object({ 123 | * password: z.string().min(8), 124 | * oldPassword: z.string().min(8), 125 | * }) 126 | * .refine((obj) => obj.password !== obj.oldPassword), 127 | * ); 128 | * 129 | * @example 130 | * 131 | * import { zodObject } from '@nixle/zod'; 132 | * 133 | * const { validatePartial } = zodObject({ 134 | * email: z.string().email(), 135 | * password: z.string().min(8), 136 | * }); 137 | * 138 | * @param options 139 | * 140 | * @example 141 | * const { validate } = zodObject((z) => ({ 142 | * email: z.string().email(), 143 | * password: z.string().min(8), 144 | * }), { 145 | * message: 'Custom message', 146 | * statusCode: StatusCode.BAD_REQUEST, 147 | * }); 148 | */ 149 | export const zodObject: ZodObject = (shape, options) => { 150 | const parse = (data: any, { partial }: { partial: boolean }) => { 151 | if (typeof shape === 'function') { 152 | const _shape = shape(z); 153 | 154 | if (_shape instanceof z.ZodObject) { 155 | return partial ? _shape.partial().parseAsync(data) : _shape.parseAsync(data); 156 | } 157 | 158 | if (_shape instanceof z.ZodEffects) { 159 | if (partial) { 160 | console.warn('Partial validation is not supported with ZodEffects'); 161 | } 162 | 163 | return _shape.parseAsync(data); 164 | } 165 | 166 | return partial 167 | ? z.object(_shape).partial().parseAsync(data) 168 | : z.object(_shape).parseAsync(data); 169 | } 170 | 171 | if (shape instanceof z.ZodObject) { 172 | return partial ? shape.partial().parseAsync(data) : shape.parseAsync(data); 173 | } 174 | 175 | return partial ? z.object(shape).partial().parseAsync(data) : z.object(shape).parseAsync(data); 176 | }; 177 | 178 | const tryCatch = async (callback: () => any) => { 179 | try { 180 | return await callback(); 181 | } catch (e) { 182 | const error = e as z.ZodError; 183 | 184 | const paths = error.errors 185 | .filter(({ path }) => path) 186 | .reduce( 187 | (acc, curr) => ({ 188 | ...acc, 189 | [curr.path.join('.')]: curr.message, 190 | }), 191 | {}, 192 | ); 193 | 194 | throw createError({ 195 | message: options?.message || 'Validation error', 196 | statusCode: options?.statusCode || StatusCode.BAD_REQUEST, 197 | details: paths ? { paths } : { errors: error.errors }, 198 | }); 199 | } 200 | }; 201 | 202 | const validate = async (data: any) => tryCatch(() => parse(data, { partial: false })); 203 | const validatePartial = async (data: any) => tryCatch(() => parse(data, { partial: true })); 204 | 205 | return { 206 | validate, 207 | validatePartial, 208 | $infer: {} as any, 209 | $inferPartial: {} as any, 210 | }; 211 | }; 212 | 213 | export const zodPlugin = () => 214 | createPlugin('zod', ({ extendServiceContext, extendRouterContext }) => { 215 | extendRouterContext({ zodObject }); 216 | extendServiceContext({ zodObject }); 217 | }); 218 | --------------------------------------------------------------------------------