SpaceNode
A complete CRUD REST API with authentication, validation, and RBAC.
my-api/
├── app.js
├── package.json
└── modules/
├── auth/
│ ├── module.js
│ ├── auth.controller.js
│ ├── auth.service.js
│ └── auth.dto.js
└── products/
├── module.js
├── products.controller.js
├── products.service.js
└── products.dto.js
import { createApp, defineAuth } from 'SpaceNode'
import jwt from 'jsonwebtoken'
const SECRET = process.env.JWT_SECRET || 'org-secret-key'
defineAuth(async (token) => {
try {
return jwt.verify(token, SECRET)
} catch {
return null
}
})
const app = await createApp({
pipe: ['cors', 'logger'],
openapi: { title: 'Products API', version: '1.0.0' },
})
app.setRoute('GET', '/health', ({ send }) => send({ status: 'ok' }))
app.listen(3000)
export default {
name: 'auth',
prefix: '/auth',
routes: [
['POST', '/login', 'login', ['rateLimit:10', 'dto:loginDto']],
['POST', '/register', 'register', ['dto:registerDto']],
['GET', '/me', 'me', ['auth']],
],
}
import { dto } from 'SpaceNode'
export const loginDto = dto({
email: ['string', 'required', 'email'],
password: ['string', 'required', 'min:6'],
})
export const registerDto = dto({
name: ['string', 'required', 'min:2', 'max:50'],
email: ['string', 'required', 'email'],
password: ['string', 'required', 'min:6'],
role: ['string', 'enum:customer,seller', 'default:customer'],
})
export async function login(request, services) {
const { body, send, check, cookie } = request
const { authService } = services
const result = await authService.login(body.email, body.password)
check(result.success, result.status, result.error)
cookie('token', result.data.token, { httpOnly: true, maxAge: 86400 })
send(result.data)
}
export async function register(request, services) {
const { body, send, guard, emit } = request
const { authService } = services
const exists = await authService.findByEmail(body.email)
guard(exists, 409, 'Email already registered')
const user = await authService.register(body)
await emit('auth:registered', { userId: user.id })
send(201, user)
}
export function me({ user, send }) {
send(user)
}
export default {
name: 'products',
prefix: '/products',
routes: [
['GET', '/', 'list'],
['GET', '/:id', 'getById'],
['POST', '/', 'create', ['auth', 'role:seller,admin', 'dto:createProductDto']],
['PUT', '/:id', 'update', ['auth']],
['DELETE', '/:id', 'remove', ['auth', 'role:admin']],
],
}
export async function list(request, services) {
const { query, send } = request
const { productService } = services
const page = Number(query.page) || 1
const limit = Number(query.limit) || 20
const products = await productService.list({ page, limit, q: query.q })
send(products)
}
export async function getById(request, services) {
const { params, send, check } = request
const { productService } = services
const product = await productService.findById(params.id)
check(product, 404, 'Product not found')
send(product)
}
export async function create(request, services) {
const { body, user, send, emit } = request
const { productService } = services
const product = await productService.create({ ...body, sellerId: user.id })
await emit('product:created', { id: product.id })
send(201, product)
}
export async function update(request, services) {
const { params, body, user, send, check } = request
const { productService } = services
const product = await productService.findById(params.id)
check(product, 404, 'Product not found')
check(product.sellerId === user.id || user.role === 'admin', 403, 'Forbidden')
const updated = await productService.update(params.id, body)
send(updated)
}
export async function remove(request, services) {
const { params, send, check } = request
const { productService } = services
const product = await productService.findById(params.id)
check(product, 404, 'Product not found')
await productService.delete(params.id)
send(204)
}
node app.js and you have a fully working REST API with auth, RBAC, validation, CORS, logging, and auto-generated OpenAPI docs.