npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@olimjonkamalov/nest-starter

v0.1.1

Published

NestJS backend starter kit: pagination, {data,meta} javob formati, 422 validatsiya xatolari, rol/auth dekoratorlari va numeric(14,2) uchun aniq pul arifmetikasi

Downloads

119

Readme

@olimjonkamalov/nest-starter

NestJS backend uchun tayyor global qatlam: sahifalash, yagona javob va xato formati, auth dekoratorlari, numeric(14,2) uchun aniq pul arifmetikasi va deploy shablonlari (Docker / nginx / PM2 / GitLab CI).

Har bir yangi loyihada qayta yoziladigan kodni bir marta o'rnatib ishlatish uchun.

npm i @olimjonkamalov/nest-starter

Mundarija


Tez boshlash

main.ts da bitta chaqiruv butun HTTP qatlamini standartga soladi:

import { NestFactory } from '@nestjs/core';
import { setupApp } from '@olimjonkamalov/nest-starter';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  setupApp(app, {
    globalPrefix: 'api/v1',
    corsOrigins: process.env.CORS_ORIGINS?.split(','),
  });

  await app.listen(process.env.PORT ?? 8000);
}
bootstrap();

setupApp quyidagilarni ulaydi:

| Nima | Natija | |---|---| | createValidationPipe() | Validatsiya xatolari 422 va { maydon: xabar } ko'rinishida | | AllExceptionsFilter | Barcha xatolar yagona formatda, 500 da ichki tafsilot sizib chiqmaydi | | ResponseInterceptor | Muvaffaqiyatli javoblar { data: ... } ichida | | setGlobalPrefix | api/v1 | | enableCors | Faqat ro'yxatdagi domenlar |

Har birini alohida ham ulash mumkin (pastda).


Sahifalash (pagination)

1. DTO

import { PaginationQueryDto } from '@olimjonkamalov/nest-starter';

export class QueryProductsDto extends PaginationQueryDto {
  // page, per_page, sort, include allaqachon bor
}

Qabul qilinadi: ?page=2&per_page=50&sort=-created_at. per_page maksimum 200 (bitta so'rov bilan butun bazani tortib ololmaydi), default 20.

2. Servisda

import { paginate, PaginatedResult } from '@olimjonkamalov/nest-starter';

const SORTABLE = {
  id: 'product.id',
  name: 'product.name',
  price: 'product.price',
  created_at: 'product.createdAt',
};

async findAll(query: QueryProductsDto): Promise<PaginatedResult<ProductDto>> {
  const qb = this.repo.createQueryBuilder('product')
    .leftJoinAndSelect('product.category', 'category');

  if (query.filter?.search) {
    qb.andWhere('product.name ILIKE :s', { s: `%${query.filter.search}%` });
  }

  return paginate(qb, {
    ...query,
    sortable: SORTABLE,
    defaultSort: { column: 'product.createdAt', direction: 'DESC' },
    tieBreaker: { column: 'product.id' },
    map: toProductResponse,
  });
}

sortable — xavfsizlik chegarasi. Ro'yxatda yo'q ustun nomi butunlay e'tiborsiz qoldiriladi va defaultSort ishlatiladi, ya'ni ?sort=id;DROP TABLE kabi qiymat SQL'ga tushmaydi.

tieBreaker nima uchun kerak. Faqat created_at bo'yicha saralaganda bir xil soniyada yaratilgan yozuvlar tartibi har so'rovda o'zgarishi mumkin — natijada 2-sahifada 1-sahifadagi yozuv qayta chiqadi yoki umuman tushib qoladi. tieBreaker: { column: 'product.id' } buni yopadi.

3. Javob

{
  "data": [ { "id": 1, "name": "..." } ],
  "meta": {
    "current_page": 2,
    "per_page": 20,
    "total": 53,
    "last_page": 3,
    "from": 21,
    "to": 40
  }
}

Qo'lda qurish

QueryBuilder ishlatmasangiz (findAndCount, xom SQL):

import { buildPaginatedResult, getSkipTake } from '@olimjonkamalov/nest-starter';

const { skip, take, page, perPage } = getSkipTake(query.page, query.per_page);
const [items, total] = await this.repo.findAndCount({ skip, take });
return buildPaginatedResult(items.map(toDto), total, page, perPage);

Javob formati

ResponseInterceptor yagona shaklni kafolatlaydi — frontend har doim response.data.data dan o'qiydi:

return { id: 1 };          // -> { "data": { "id": 1 } }
return [1, 2];             // -> { "data": [1, 2] }
return paginate(...);      // -> { "data": [...], "meta": {...} }  (o'ralmaydi)

Javobda allaqachon data kaliti bo'lsa, interceptor unga TEGMAYDI — shuning uchun PaginatedResult ikki marta o'ralib qolmaydi.


Xato formati

// 422 — maydon xatolari
{
  "data": { "phone": "Bu raqam band" },
  "message": "Ma'lumotlar noto'g'ri",
  "msg": "Ma'lumotlar noto'g'ri",
  "errorMessage": "Ma'lumotlar noto'g'ri"
}

// qolgan holatlar
{
  "message": "Topilmadi",
  "msg": "Topilmadi",
  "errorMessage": "Topilmadi",
  "statusCode": 404
}

message uch kalitda takrorlanadi (message / msg / errorMessage) — turli frontend kutubxonalari turlichasini o'qiydi, natijada hech qayerda "undefined" chiqmaydi.

500 xatolarida ichki xabar tashqariga chiqmaydi. SQL matni, stack va boshqa tafsilotlar faqat logga yoziladi, foydalanuvchi esa umumiy xabar ko'radi.

Maydon xatosini qo'lda otish

import { ValidationException } from '@olimjonkamalov/nest-starter';

if (await this.repo.exists({ where: { phone } })) {
  throw new ValidationException({ phone: 'Bu raqam allaqachon band' });
}

Mashina o'qiydigan kod bilan

import { CodedException } from '@olimjonkamalov/nest-starter';

throw new CodedException('SUBSCRIPTION_EXPIRED', 'Obuna muddati tugagan', 403);
// -> { message, msg, errorMessage, statusCode: 403, code: "SUBSCRIPTION_EXPIRED" }

Frontend interceptor'i code bo'yicha maxsus muomala qiladi (masalan foydalanuvchini obuna sahifasiga yo'naltiradi), matnni tahlil qilmaydi.

Xabarlarni o'zgartirish

app.useGlobalFilters(
  new AllExceptionsFilter({
    serverErrorMessage: 'Server error',
    validationMessage: 'Validation failed',
    databaseErrorMessage: 'Query failed',
  }),
);

Validatsiya

import { createValidationPipe } from '@olimjonkamalov/nest-starter';

app.useGlobalPipes(createValidationPipe());
  • whitelist: true — DTO'da e'lon qilinmagan maydonlar tashlab yuboriladi
  • Status 422 (NestJS default 400 emas)
  • Ichma-ich xatolar nuqtali yo'lga yoyiladi: { "filter.search": "matn kiriting" }
  • Bitta maydonda bir nechta qoida buzilsa — birinchi xabar (foydalanuvchiga ro'yxat emas, bitta aniq gap kerak)

Auth: rollar va dekoratorlar

import { APP_GUARD } from '@nestjs/core';
import { RolesGuard } from '@olimjonkamalov/nest-starter';

@Module({
  providers: [
    { provide: APP_GUARD, useClass: JwtAuthGuard },  // o'zingizniki
    { provide: APP_GUARD, useClass: RolesGuard },
  ],
})
export class AppModule {}
import { CurrentUser, Public, Roles, AuthenticatedUser } from '@olimjonkamalov/nest-starter';

@Roles('superadmin', 'admin')
@Controller('users')
export class UsersController {
  @Public()                       // auth talab qilinmaydi
  @Post('login')
  login() {}

  @Roles('superadmin')            // klass darajasidagini ALMASHTIRADI
  @Delete(':id')
  remove() {}

  @Get('me')
  me(@CurrentUser() user: AuthenticatedUser) {
    return user;
  }

  @Get('my-id')
  myId(@CurrentUser('id') id: number) {
    return id;
  }
}

RolesGuard getAllAndOverride ishlatadi: metoddagi @Roles klassdagini almashtiradi, qo'shmaydi. Ya'ni umumiy ruxsatli kontrollerda bitta metodni torroq qilib qo'yish mumkin.

@Public() ishlashi uchun auth guard'ingiz IS_PUBLIC_KEY ni tekshirishi kerak:

import { IS_PUBLIC_KEY } from '@olimjonkamalov/nest-starter';

canActivate(context: ExecutionContext) {
  const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
    context.getHandler(),
    context.getClass(),
  ]);
  if (isPublic) return true;
  return super.canActivate(context);
}

Pul va numeric(14,2)

Pulni hech qachon number da hisoblamang: 0.1 + 0.2 === 0.30000000000000004. Xato tiyin darajasida boshlanadi va hisobotlarda yig'ilib boradi.

Bu paketdagi amallar BigInt ustida ishlaydi va natija har doim ikki xonali kasrli string bo'ladi.

import {
  addDecimals, subtractDecimals, multiplyDecimals,
  compareDecimals, sumDecimals, toDecimal,
} from '@olimjonkamalov/nest-starter';

addDecimals('0.10', '0.20');        // "0.30"
subtractDecimals('100.00', '150.50'); // "-50.50"
multiplyDecimals('12.50', '3');     // "37.50"
sumDecimals(['10.10', '20.20']);    // "30.30"
compareDecimals('10.00', '9.99');   // 1
toDecimal(5);                        // "5.00"

Kirish qiymati 2 xonadan ortiq bo'lsa qisqaradi — bu numeric(14,2) ustunining o'z semantikasi (toDecimal('33.335') === '33.33').

Entity

import { decimalTransformer } from '@olimjonkamalov/nest-starter';

@Column({ type: 'numeric', precision: 14, scale: 2, transformer: decimalTransformer })
amount: string;   // number EMAS

DTO

import { IsDecimalAmount, toDecimalString } from '@olimjonkamalov/nest-starter';

export class CreatePaymentDto {
  @Transform(({ value }) => toDecimalString(value))
  @IsDecimalAmount({ min: 0, exclusiveMin: true })
  amount: string;
}

TypeORM transformerlari

import { dateTransformer } from '@olimjonkamalov/nest-starter';

@Column({ type: 'date', transformer: dateTransformer, nullable: true })
due_date: string | null;    // "2026-09-24"

pg drayveri date ustunini JS Date ga aylantiradi va vaqt zonasi tufayli kun bir kunga siljib ketishi mumkin. Bu transformer qiymatni har doim "YYYY-MM-DD" string sifatida saqlaydi.


Yordamchi funksiyalar

import {
  toBoolean, parseDurationToSeconds,
  formatMoney, formatDate, escapeHtml,
  normalizePhone, isValidPhone, formatPhone,
} from '@olimjonkamalov/nest-starter';

toBoolean('1');                       // true   (query string uchun)
parseDurationToSeconds('7d');         // 604800
formatMoney('45000.00');              // "45 000"
formatDate('2026-09-24');             // "24.09.2026"
escapeHtml('<b>a</b>');               // "&lt;b&gt;a&lt;/b&gt;"
normalizePhone('90 123 45 67');       // "998901234567"
formatPhone('998901234567');          // "+998 90 123-45-67"

escapeHtml — Telegram parse_mode: HTML ga foydalanuvchi matnini qo'yishdan oldin shart, aks holda ismdagi < xabarni buzadi.

toBoolean bilan query filtri:

@Transform(({ value }) => toBoolean(value))
@IsOptional()
@IsBoolean()
is_active?: boolean;     // ?filter[is_active]=1

Deploy shablonlari

Shablonlarni loyihangizga ko'chirish:

npx @olimjonkamalov/nest-starter init all        # hammasi
npx @olimjonkamalov/nest-starter init docker     # faqat Docker
npx @olimjonkamalov/nest-starter init nginx
npx @olimjonkamalov/nest-starter init pm2
npx @olimjonkamalov/nest-starter init ci
npx @olimjonkamalov/nest-starter init env
npx @olimjonkamalov/nest-starter init all --force  # mavjud fayllarni ham qayta yozish

Mavjud fayl qayta yozilmaydi (--force bo'lmasa) — loyihangizdagi sozlamalar tasodifan yo'qolmaydi.

Nima ko'chadi:

| Guruh | Fayl | Qayerga | |---|---|---| | docker | Dockerfile, docker-compose.yml, .dockerignore | ildiz | | nginx | api.conf, spa.conf | deploy/nginx/ | | pm2 | ecosystem.config.js | ildiz | | ci | .gitlab-ci.yml | ildiz | | env | .env.example | ildiz |

Docker

Ikki bosqichli (multi-stage) build: birinchi bosqichda kod kompilyatsiya qilinadi, ikkinchisiga faqat dist va production paketlari ko'chadi — image hajmi bir necha barobar kichik bo'ladi.

  • root ostida ishlamaydi (alohida app foydalanuvchisi)
  • HEALTHCHECK — orkestrator konteyner tirikligini biladi
  • uploads/ volume'da — deploy'da yuklangan fayllar yo'qolmaydi
  • docker-compose.yml da Postgres porti faqat 127.0.0.1 ga ochilgan, ya'ni internetdan bazaga to'g'ridan-to'g'ri kirib bo'lmaydi
cp .env.example .env    # to'ldiring
docker compose up -d
docker compose logs -f api

nginx

deploy/nginx/api.conf — API uchun reverse proxy, spa.conf — frontend build uchun. <API_DOMAIN>, <APP_DOMAIN>, <APP_DIR>, <DIST_DIR> larni almashtiring.

sudo cp deploy/nginx/api.conf /etc/nginx/sites-available/api.conf
sudo ln -s /etc/nginx/sites-available/api.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d api.example.com

SPA konfigida index.html keshlanmaydi, /assets/ esa bir yil keshlanadi — aks holda foydalanuvchi deploy'dan keyin ham eski bundle'da qolib ketadi.

PM2

pm2 start ecosystem.config.js --env production
pm2 save && pm2 startup      # server qayta yuklansa avtomatik ko'tariladi
pm2 logs api --lines 100
pm2 restart api --update-env # .env o'zgarganda SHART

--update-env siz PM2 eski muhit o'zgaruvchilarini xotirada saqlab qoladi va .env dagi yangi qiymat ishlamaydi — eng ko'p vaqt yeydigan xatolardan biri.

GitLab CI

.gitlab-ci.yml — main ga merge bo'lganda serverga rsync + npm ci + build + migratsiya + pm2 restart. Variables: SSH_HOST, SSH_USER, SSH_PASS, APP_DIR.


To'liq API ro'yxati

Sahifalash: PaginationQueryDto, paginate, buildPaginatedResult, emptyPaginatedResult, parseSort, getSkipTake, PaginatedResult, PaginationMeta, SortableColumns, ParsedSort, SortDirection

HTTP: setupApp, ResponseInterceptor, AllExceptionsFilter, createValidationPipe, flattenValidationErrors, ValidationException, CodedException, FieldErrors

Auth: Public, IS_PUBLIC_KEY, Roles, ROLES_KEY, RolesGuard, CurrentUser, AuthenticatedUser

Pul: toDecimal, addDecimals, subtractDecimals, multiplyDecimals, negateDecimal, sumDecimals, compareDecimals, isDecimalNegative, isDecimalZero, decimalTransformer, toDecimalString, IsDecimalAmount

TypeORM: dateTransformer

Utils: toBoolean, parseDurationToSeconds, formatMoney, formatDate, escapeHtml, normalizePhone, isValidPhone, formatPhone


Talablar

Node.js 18+. Peer dependency sifatida (loyihangizda allaqachon bor):

@nestjs/common, @nestjs/core, class-validator, class-transformer, rxjs, reflect-metadata, typeorm (ixtiyoriy — faqat transformerlar uchun).


npm ga chiqarish

# 1) Bir marta: npm akkauntiga kirish
npm login

# 2) Nomni tekshirish (404 = bo'sh)
npm view @olimjonkamalov/nest-starter

# 3) Nashrdan oldin — testlar va build (prepublishOnly buni o'zi qiladi)
npm test
npm run build

# 4) Paket ichiga nima tushishini KO'RISH (nashr qilmasdan)
npm pack --dry-run

# 5) Nashr
npm publish --access public

# 6) Keyingi versiyalar (semver)
npm version patch   # 0.1.0 -> 0.1.1  (xato tuzatish)
npm version minor   # 0.1.0 -> 0.2.0  (yangi imkoniyat)
npm version major   # 0.1.0 -> 1.0.0  (buzuvchi o'zgarish)
git push --follow-tags
npm publish

Paket scoped (@olimjonkamalov/...) — shuning uchun --access public SHART, aks holda npm uni private qilib nashr qilmoqchi bo'ladi va bepul akkauntda xato beradi. publishConfig.access: public allaqachon qo'yilgan.

Nashrdan oldin boshqa loyihada sinab ko'rish:

npm pack                    # olimjonkamalov-nest-starter-0.1.1.tgz
cd ../boshqa-loyiha
npm i ../nest-starter/olimjonkamalov-nest-starter-0.1.1.tgz

Litsenziya

MIT