Skip to content

Quick Start

Get a fully functional Nevr API running in under 5 minutes.

Create a New Project

bash
npm create nevr@latest my-api
cd my-api

Follow the prompts to select your preferred:

  • Database: SQLite (default), PostgreSQL, or MySQL
  • HTTP Framework: Express (default) or Hono

CLI Options Reference

OptionValuesDefault
--databasesqlite, postgresql, mysqlsqlite
--httpexpress, hono, fastifyexpress
--skip-install-false
--git-true
bash
# Example: PostgreSQL + Hono, skip git init
npm create nevr@latest my-api -- --database postgresql --http hono --no-git

Project Structure

After creation, you'll have:

my-api/
├── src/
│   ├── entities/
│   │   └── user.ts          # Your entity definitions
│   ├── config.ts             # Nevr configuration
│   └── server.ts             # HTTP server entry point
├── prisma/
│   └── schema.prisma         # Generated Prisma schema
├── package.json
└── tsconfig.json

Key Files Explained

FilePurpose
src/entities/*.tsEntity definitions (source of truth)
src/config.tsNevr instance configuration
src/server.tsHTTP adapter setup
prisma/schema.prismaAuto-generated database schema

Start Development

bash
# Install dependencies
npm install

# Push database schema
npm run db:push

# Start dev server
npm run dev

Your API is now running at http://localhost:3000.

NPM Scripts Reference

ScriptCommandDescription
devtsx watch src/server.tsStart dev server with hot reload
generatenevr generateRegenerate Prisma schema from entities
db:pushprisma db pushPush schema to database
db:migrateprisma migrate devCreate migration
buildtscBuild TypeScript

Try It Out

All CRUD endpoints are automatically generated for your entities:

Generated Endpoints Reference

MethodEndpointDescription
GET/api/usersList all users (with pagination, filtering)
GET/api/users/:idGet a single user by ID
POST/api/usersCreate a new user
PATCH/api/users/:idUpdate a user (partial)
PUT/api/users/:idReplace a user (full)
DELETE/api/users/:idDelete a user

Create a User

bash
curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{"name": "John Doe", "email": "john@example.com"}'

Response:

json
{
  "id": "clx...",
  "name": "John Doe",
  "email": "john@example.com",
  "createdAt": "2025-01-15T10:30:00.000Z",
  "updatedAt": "2025-01-15T10:30:00.000Z"
}

List All Users (with Query Options)

bash
# Basic list
curl http://localhost:3000/api/users

# With pagination
curl "http://localhost:3000/api/users?skip=0&take=10"

# With filtering
curl "http://localhost:3000/api/users?where[email][contains]=@example.com"

# With sorting
curl "http://localhost:3000/api/users?orderBy[createdAt]=desc"

# Include relations
curl "http://localhost:3000/api/users?include=posts"

Query Parameters Reference

ParameterExampleDescription
skip?skip=10Skip N records (offset)
take?take=25Limit to N records
filter?filterFilter conditions
sort?sortSort order
include?include=relationInclude related records

Get a Single User

bash
curl http://localhost:3000/api/users/clx...

Update a User

bash
curl -X PATCH http://localhost:3000/api/users/clx... \
  -H "Content-Type: application/json" \
  -d '{"name": "Jane Doe"}'

Delete a User

bash
curl -X DELETE http://localhost:3000/api/users/clx...

Add Your First Entity

Create a new entity in src/entities/post.ts:

typescript
import { entity, string, text, boolean, belongsTo } from "nevr"
import { user } from "./user"

export const post = entity("post", {
  title: string.min(1).max(200),      // Required, 1-200 chars
  content: text,                       // Long text field
  published: boolean.default(false),   // Default to draft
  author: belongsTo(() => user),       // Relation to user
})

Field Types Quick Reference

TypeImportDescription
stringstringShort text (varchar)
texttextLong text (unlimited)
intintInteger number
floatfloatDecimal number
boolean / boolboolean, boolTrue/false
datetimedatetimeDate and time
jsonjsonJSON object
belongsTobelongsTo(() => Entity)Many-to-one relation
hasManyhasMany(() => Entity)One-to-many relation
hasOnehasOne(() => Entity)One-to-one relation

Register it in src/config.ts:

typescript
import { nevr } from "nevr"
import { prisma } from "nevr/drivers/prisma"
import { PrismaClient } from "@prisma/client"
import { user } from "./entities/user"
import { post } from "./entities/post"

export const api = nevr({
  entities: [user, post],  
  driver: prisma(new PrismaClient()),
})

Regenerate and push:

bash
npm run generate
npm run db:push

Now you have a full CRUD API for posts at /api/posts.

Add Authorization

Make posts owned by their author:

typescript
export const post = entity("post", {
  title: string.min(1).max(200),
  content: text,
  published: boolean.default(false),
  author: belongsTo(() => user),
})
  .ownedBy("author")  //  Sets owner + default rules
  .rules({            //  Override specific rules
    create: ["authenticated"],  
    read: ["everyone"],         
    update: ["owner"],          
    delete: ["owner"],          
  })                  

What .ownedBy() Does

When you call .ownedBy("author"), Nevr automatically:

  1. Sets the author relation as the owner field
  2. Applies default rules if not overridden:
OperationDefault Rule
createauthenticated
readeveryone
updateowner
deleteowner
listeveryone

Built-in Rules Reference

RuleDescription
everyoneAnyone (no auth required)
authenticatedAny logged-in user
ownerOnly the resource owner
adminUsers with admin role
ownerOrAdminOwner or admin

Now:

  • Anyone can read posts
  • Only authenticated users can create posts
  • Only the author can update or delete their own posts

Add a Custom Action

Add a "publish" action to posts:

typescript
import { entity, string, text, boolean, belongsTo, action } from "nevr"

export const post = entity("post", {
  title: string.min(1).max(200),
  content: text,
  published: boolean.default(false),
  author: belongsTo(() => user),
})
  .ownedBy("author")
  .rules({
    create: ["authenticated"],
    read: ["everyone"],
    update: ["owner"],
    delete: ["owner"],
  })
  .actions({  
    publish: action()  
      .onResource()    //  Requires :id in URL
      .rules("owner")  //  Only owner can publish
      .handler(async (ctx) => {  
        const post = await ctx.driver.update(  
          "post",  
          { id: ctx.resourceId },  
          { published: true }  
        )  
        return post  
      }),  
  })  

Action Builder Methods Reference

MethodDescription
action()Create new action
.get() / .post()Set HTTP method (default: POST)
.onResource()Require resource ID in URL
.rules(...rules)Set authorization rules
.input(schema)Define input validation schema
.handler(fn)Set the action handler
.workflow(steps)Use saga pattern with compensation
.meta({ summary, description })Add OpenAPI metadata

Action Context Properties

PropertyTypeDescription
ctx.resourceIdstringThe resource ID from URL
ctx.inputTInputValidated input data
ctx.userUser | nullAuthenticated user
ctx.driverDriverDatabase driver
ctx.entitystringEntity name

Call it:

bash
curl -X POST http://localhost:3000/api/posts/clx.../publish

What's Next?

You've just built a fully functional API with:

  • ✅ CRUD operations
  • ✅ Relationships
  • ✅ Authorization rules
  • ✅ Custom actions

Explore more:

TopicLinkDescription
FieldsFields OverviewAll field types and modifiers
EntitiesDefining EntitiesAdvanced entity configuration
ActionsActions OverviewCustom operations and workflows
AuthenticationAuth PluginAdd user authentication
ServicesDI ContainerDependency injection
GeneratorsCode GenerationPrisma, TypeScript, OpenAPI

Released under the MIT License.