nevr
v0.3.5
Published
The Entity-First Full Stack Framework for TypeScript. Define your entities, never write boilerplate again.
Maintainers
Readme
Nevr
Nevr write boilerplate again — Entity-first, type-safe API framework for TypeScript
⚠️ Beta Software: Nevr is under active development. APIs may change before v1.0.
✨ Features
- 🚀 Entity-first design — Define entities, get complete CRUD APIs automatically
- 🔒 End-to-end type safety — Full TypeScript inference from database to client
- 🔐 Built-in authentication — Email/password, OAuth, sessions, JWT
- 🔌 Framework agnostic — Works with Express, Hono, and more
- 🗄️ Database agnostic — Prisma driver included, more coming soon
- 🧩 Unified Plugin System — Extend with auth, timestamps, payments, storage plugins
- ⚡ Zero config — Sensible defaults, full customization when needed
- 📦 Auto-generated clients — Type-safe API clients for your frontend
- 🔄 Workflow Engine — Multi-step transactions with compensation (saga pattern)
- 💉 Service Container — Functional dependency injection with lifecycle management
- ✅ Cross-field Validation — Complex business rules with declarative syntax
📦 Installation
npm install nevr
# or
pnpm add nevr
# or
yarn add nevr🆕 Create a New Nevr Project
# Quick installation with project scaffolding
npm create nevr@latest my-nevr-app🚀 Quick Start
import { entity, string, text, belongsTo, nevr } from "nevr"
import { expressAdapter } from "nevr/adapters/express"
import { prisma } from "nevr/drivers/prisma"
import { PrismaClient } from "@prisma/client"
import express from "express"
// 1️⃣ Define your entities
const user = entity("user", {
email: string.unique(),
name: string.min(1).max(100),
}).build()
const post = entity("post", {
title: string.min(1).max(200),
content: text,
author: belongsTo(() => user),
})
.ownedBy("author")
.build()
// 2️⃣ Create your API
const api = nevr({
entities: [user, post],
driver: prisma(new PrismaClient()),
})
// 3️⃣ Mount to Express
const app = express()
app.use("/api", expressAdapter(api, { cors: true }))
app.listen(3000, () => console.log("API running at http://localhost:3000/api"))That's it! You now have a full REST API with:
GET/POST /api/users— List & create usersGET/PUT/DELETE /api/users/:id— Read, update, delete userGET/POST /api/posts— List & create postsGET/PUT/DELETE /api/posts/:id— Read, update, delete post
📖 Entity-First DSL
Field Types
import { entity, string, text, int, float, bool, datetime, json, belongsTo, email } from "nevr"
const task = entity("task", {
// String fields with validation
title: string.min(1).max(200), // VARCHAR with validation
slug: string.unique(), // Unique constraint
description: text.optional(), // Long text, nullable
// Number fields
priority: int.default(0).min(0).max(5), // Integer with range
progress: float.default(0), // Floating point
// Boolean & DateTime
completed: bool.default(false),
dueDate: datetime.optional(),
// JSON data
metadata: json.optional(),
// Relations
project: belongsTo(() => project), // Many-to-one relation
assignee: belongsTo(() => user),
}).build()Field Modifiers
| Modifier | Description | Example |
|----------|-------------|---------|
| .optional() | Make field nullable | string.optional() |
| .unique() | Add unique constraint | string.unique() |
| .default(value) | Set default value | bool.default(false) |
| .min(n) / .max(n) | Validation bounds | string.min(1).max(100) |
| .trim() | Trim whitespace | string.trim() |
| .lower() / .upper() | Case transformation | string.lower() |
Security & Privacy (Entity-First)
Control field visibility and mutability with the Entity-First DSL:
const user = entity("user", {
email: string.email().unique(),
// 🔐 Password: auto-hashed, never returned in responses
password: string.password().omit(),
// 🔒 Protected fields: can't be set by clients
role: string.default("user").writable("none"),
emailVerified: bool.default(false).writable("none"),
// 📖 Read-only computed fields
lastLoginAt: datetime.writable("none").optional(),
}).build()| Builder Method | Description |
|----------------|-------------|
| .password() | Hash on save, compare securely |
| .omit() | Never return in API responses |
| .writable("none") | Server-only, no client writes |
| .writable("create") | Set on create only |
| .email() | Email validation |
Access Control
const post = entity("post", { /* fields */ })
.ownedBy("author") // Track ownership via 'author' field
.rules({
create: ["authenticated"], // Must be logged in
read: ["everyone"], // Public access
update: ["owner", "admin"], // Owner or admin only
delete: ["admin"], // Admin only
})
.build()Built-in Rules:
everyone— No authentication requiredauthenticated— Must be logged inowner— Must own the resource (viaownedBy)admin— Must have admin role
🔌 Adapters
Express
import { expressAdapter, devAuth } from "nevr/adapters/express"
const app = express()
app.use("/api", expressAdapter(api, {
getUser: devAuth,
cors: true
}))Hono
import { Hono } from "hono"
import { mountNevr, honoDevAuth } from "nevr/adapters/hono"
const app = new Hono()
mountNevr(app, "/api", api, { getUser: honoDevAuth })
export default app🗄️ Drivers
Prisma
import { prisma } from "nevr/drivers/prisma"
import { PrismaClient } from "@prisma/client"
const api = nevr({
entities: [user, post],
driver: prisma(new PrismaClient()),
})Generate your Prisma schema with @nevr/cli:
npx @nevr/cli generate
npx prisma db push --schema=./prisma/schema.prisma🧩 Plugins
Authentication
Full-featured auth with email/password, OAuth, sessions, and JWT:
import { auth } from "nevr/plugins/auth"
const api = nevr({
entities: [post],
plugins: [
auth({
emailAndPassword: {enabled: true},
socialProviders: {
google: { clientId: "...", clientSecret: "..." },
github: { clientId: "...", clientSecret: "..." },
},
})
],
driver: prisma(db),
})Generated routes:
POST /api/auth/sign-up— Create accountPOST /api/auth/sign-in— Sign inPOST /api/auth/sign-out— Sign outGET /api/auth/session— Get current sessionGET /api/auth/callback/:provider— OAuth callback
Timestamps
Auto-manage createdAt and updatedAt:
import { timestamps } from "nevr/plugins/timestamps"
const api = nevr({
entities: [post],
plugins: [timestamps()],
driver: prisma(db),
})🔄 Workflow Engine
Execute multi-step operations with automatic rollback (saga pattern):
import { createWorkflow, step, createEntityStep, deleteEntityStep } from "nevr"
const signUpWorkflow = createWorkflow({
name: "user-signup",
useTransaction: true,
execute: async (ctx, input) => {
// Step 1: Create user
const user = await step(ctx, {
name: "create-user",
execute: () => ctx.driver.create("user", { email: input.email }),
compensate: (result) => ctx.driver.delete("user", { id: result.id }),
})
// Step 2: Create profile
await step(ctx, {
name: "create-profile",
execute: () => ctx.driver.create("profile", { userId: user.id }),
compensate: () => ctx.driver.delete("profile", { userId: user.id }),
})
// Step 3: Send welcome email
await step(ctx, {
name: "send-email",
execute: () => sendWelcomeEmail(user.email),
retry: { maxAttempts: 3, delay: 1000 },
})
return user
}
})💉 Service Container
Functional dependency injection with lifecycle management:
const api = nevr({ entities: [...], driver })
// Register services
api.registerService("stripe", () => new Stripe(process.env.STRIPE_KEY!))
api.registerService("email", () => new EmailService(), { lifecycle: "singleton" })
// Resolve in hooks or handlers
const handlePayment = async (ctx) => {
const stripe = ctx.resolve<Stripe>("stripe")
return stripe.charges.create({ amount: 1000, currency: "usd" })
}Lifecycle Options:
singleton— Single instance for app lifetime (default)transient— New instance on each resolvescoped— New instance per request scope
✅ Cross-Field Validation
Validate complex business rules:
const order = entity("order", {
startDate: datetime,
endDate: datetime,
minQty: int,
maxQty: int,
})
.validate({
dateRange: {
fn: (data) => data.endDate > data.startDate,
message: "End date must be after start date",
fields: ["startDate", "endDate"],
},
qtyRange: {
fn: (data) => data.maxQty >= data.minQty,
message: "Max quantity must be >= min quantity",
},
})
.build()🔍 Query API
All list endpoints support powerful query parameters:
# Filtering
GET /api/posts?filter[published]=true
GET /api/posts?filter[authorId]=123
# Sorting
GET /api/posts?sort=createdAt # Ascending
GET /api/posts?sort=-createdAt # Descending
# Pagination
GET /api/posts?limit=20&offset=0
# Include relations
GET /api/posts?include=author
GET /api/posts?include=author,comments🔷 Type Inference
Get end-to-end type safety from server to client:
// Server
import { nevr, entity, string } from "nevr"
export const api = nevr({ entities: [...], driver })
export type API = typeof api
// Client (can be in a different package!)
import type { API } from "../server"
import { createTypedClient } from "nevr/client"
const client = createTypedClient<API>({ baseURL: "/api" })
// Full autocomplete & type checking!
const users = await client.users.list()
const user = await client.users.create({ email: "..." })📚 Related Packages
| Package | Description |
|---------|-------------|
| nevr | Core framework (this package) |
| @nevr/cli | CLI for schema generation |
| @nevr/generator | Prisma/TypeScript generator |
| create-nevr | Project scaffolder |
🤝 Contributing
We welcome contributions! See our Contributing Guide for details.
📄 License
MIT © Nevr Contributors
