https://github.com/slaega/db-validation
validation library for Prisma‑backed services. It lets you declare common database checks—like “exists” or “unique”—via a fluent builder, and integrates them into your service or controller via decorators.
https://github.com/slaega/db-validation
clean-code nestjs nestjs-backend prisma validation-rules validation-service
Last synced: 10 months ago
JSON representation
validation library for Prisma‑backed services. It lets you declare common database checks—like “exists” or “unique”—via a fluent builder, and integrates them into your service or controller via decorators.
- Host: GitHub
- URL: https://github.com/slaega/db-validation
- Owner: slaega
- License: mit
- Created: 2025-04-15T13:53:31.000Z (11 months ago)
- Default Branch: main
- Last Pushed: 2025-05-05T16:58:58.000Z (10 months ago)
- Last Synced: 2025-05-09T01:44:43.746Z (10 months ago)
- Topics: clean-code, nestjs, nestjs-backend, prisma, validation-rules, validation-service
- Language: TypeScript
- Homepage:
- Size: 574 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# @slaega/db-validation
**@slaega/db-validation** is a NestJS‑compatible validation library for Prisma‑backed services. It lets you declare common database checks—like “exists” or “unique”—via a fluent builder, and integrates them into your service or controller via decorators.
---
## 🚀 Features
- **Existence check**: Verify that a record exists before proceeding.
- **Uniqueness check**: Ensure a field is unique (optionally excluding the current record).
- **Custom rules**: Extend with your own validation logic.
- **Decorator integration**: Hook validations into your service methods with a single decorator.
---
## 📦 Installation
```bash
yarn add @slaega/db-validation
# or
npm install @slaega/db-validation
```
Since this package declares NestJS and Prisma as **peerDependencies**, make sure your app has them installed too:
```bash
yarn add @nestjs/common @nestjs/core @prisma/client reflect-metadata
```
---
## ⚙️ Usage
### 1. Define validation rules
Create a class that returns a `DbValidationBuilder` instance for each method you want to guard:
```ts
import { DbValidationBuilder } from '@slaega/db-validation';
export class PostValidationRules {
create(userId: number, data: { title: string }) {
return DbValidationBuilder.new()
.exists('User', { id: userId }, 'User not found')
.unique('Post', { title: data.title }, undefined, 'Post title already in use');
}
update(userId: number, postId: number, data: { title: string }) {
return DbValidationBuilder.new()
.exists('Post', { id: postId, authorId: userId }, 'Post not found')
.unique('Post', { title: data.title }, { id: postId }, 'Title conflict');
}
findOne(userId: number, postId: number) {
return DbValidationBuilder.new()
.exists('Post', { id: postId, authorId: userId }, 'Post not found');
}
}
```
### 2. Apply via decorator # UseDbValidationSimple
Use the built‑in decorator to run your rules automatically before the decorated method:
```ts
import { Injectable } from '@nestjs/common';
import { UseDbValidationSimple } from '@slaega/db-validation';
import { PostRepository } from './post.repository';
import { PostValidationRules } from './post.validation-rules';
@Injectable()
export class PostService {
constructor(
private readonly repo: PostRepository,
public readonly dbValidatorService: DbValidationService, // this name must match the decorator
) {}
@UseDbValidationSimple(PostValidationRules, 'create')
async createPost(userId: number, input: { title: string }) {
return this.repo.create({ ...input, authorId: userId });
}
@UseDbValidationSimple(PostValidationRules, 'update')
async updatePost(userId: number, postId: number, input: { title: string }) {
return this.repo.update(postId, input);
}
}
```
### 2. Apply via decorator # UseDbValidation
Use the built‑in decorator to run your rules automatically before the decorated method:
```ts
import { Injectable } from '@nestjs/common';
import { UseDbValidation } from '@slaega/db-validation';
import { PostRepository } from './post.repository';
import { PostValidationRules } from './post.validation-rules';
@Injectable()
export class PostService {
constructor(
private readonly repo: PostRepository,
public readonly dbValidatorService: DbValidationService, // this name must match the decorator
) {}
@UseDbValidation(PostValidationRules, 'create', (self) => self.dbValidatorService)
async createPost(userId: number, input: { title: string }) {
return this.repo.create({ ...input, authorId: userId });
}
@UseDbValidation(PostValidationRules, 'update', (self) => self.dbValidatorService)
async updatePost(userId: number, postId: number, input: { title: string }) {
return this.repo.update(postId, input);
}
}
```
> 💡 The property name `dbValidatorService` must match the default expected by the decorator (`UseDbValidationSimple`). If you'd prefer a custom name, use `UseDbValidationFrom()` instead and provide the key.
---
### 3. Without decorators (manual usage)
If you don’t want to use decorators, you can call the validator directly:
```ts
const builder = new PostValidationRules().create(userId, input);
await dbValidatorService.validate(builder);
```
---
## 🧩 Decorators API
### `@UseDbValidation`
Low-level decorator. Lets you control how the validator service is retrieved from `this`.
```ts
@UseDbValidation(RulesClass, 'methodName', (self) => self.myCustomValidator)
```
### `@UseDbValidationFrom`
Mid-level decorator. Lets you specify the property name of the validator service on the class (defaults to `validationService`).
```ts
@UseDbValidationFrom(RulesClass, 'methodName', 'dbValidatorService')
```
### `@UseDbValidationSimple`
High-level, opinionated decorator. Looks for a property named `dbValidatorService` in your class.
```ts
@UseDbValidationSimple(RulesClass, 'methodName')
```
---
## 🛠 API
#### `DbValidationBuilder`
| Method | Description |
|---------------------------------------------------|----------------------------------------------------------------------------------------------------------|
| `.exists(modelName, where, errorMessage?)` | Throws `NotFoundException` if no record matches `where`. |
| `.unique(modelName, where, exclude?, errorMessage?)` | Throws `ConflictException` if a record matching `where` exists and doesn’t match `exclude`. |
| `.dependent(modelName, where, dependentField, expectedValue, errorMessage?)` | Throws `BadRequestException` if record’s `dependentField` ≠ `expectedValue`. |
| `.equals(value, expected, errorMessage?)` | Throws `BadRequestException` if `value !== expected`. |
| `.inList(value, list, errorMessage?)` | Throws `BadRequestException` if `value` not in `list`. |
| `.notInList(value, list, errorMessage?)` | Throws `BadRequestException` if `value` is in `list`. |
| `.custom(validateFn, errorType?, errorMessage?)` | Runs arbitrary async `validateFn`; throws exception based on `errorType` (`not_found`, `conflict`, or `bad_request`). |
---
## 🔄 Regenerating Mappings
Whenever you update your Prisma schema, regenerate the client and then re-run the CLI to rebuild your TypeScript mapping:
```bash
# Direct commands:
npx prisma generate && npx db-validation
# Or via package.json scripts:
# package.json
"scripts": {
"prisma:generate": "prisma generate",
"generate:types": "npm run prisma:generate && npm run generate",
"generate": "db-validation"
}
# Then:
npm run generate:types
# or
yarn generate:types
```
This will produce a `dist/types.ts` file containing:
```ts
import { Prisma } from '@prisma/client';
export type ModelWhereMapping = {
User: Prisma.UserWhereInput;
Post: Prisma.PostWhereInput;
// …and so on for each model
};
```
---
## 🧪 Testing locally
To develop and test your package in isolation:
1. **Clone & install**
```bash
git clone https://github.com/slaega/db-validation.git
cd db-validation
yarn install
```
2. **Build**
```bash
yarn build
```
3. **Link into a consuming project**
```bash
yarn link
cd ../your-app
yarn link @slaega/db-validation
yarn install
```
4. **Run tests**
```bash
yarn test
yarn test:watch
```
---
## 🤝 Contributing
1. Fork the repo
2. Create a feature branch (`git checkout -b feature/my-change`)
3. Commit your changes (`git commit -m 'Add feature'`)
4. Push to your branch (`git push origin feature/my-change`)
5. Open a Pull Request
---
## 📄 License
This project is **MTI** [LICENSE](LICENSE) for details.
---
> Maintained by **Slaega**. Feel free to open issues on GitHub!