NestJS ile TypeScript Kullanarak Web Uygulamaları Geliştirme

Selamlar, bu yazıda Nest yani bilinen diğer adıyla NestJS hakkında ufak bir giriş yazısı yazacağım. Umarım başarılı şekilde bunu yapabilirim.

Nest kısacası verimli ve ölçeklenebilir web uygulamaları geliştirebileceğiniz bir Node.js framework’ü. Düz olarak JavaScript kullanabildiğiniz gibi TypeScript ile de geliştirme yapabiliyorsunuz.

HTTP default server yapılandırması olarak Express benzeri HTTP server frameworklerini kullanıyor. Ama isterseniz Fastify da kullanılabiliyor.

Nest yukarıda söylenen yaygın frameworkler için abstraction işlemi yapar ve ayrıca API’ları da direkt olarak sunar. Bu sayede geliştiricilerin üçüncü taraf eklentileri (third-party) daha rahat bir şekilde kullanmalarını sağlar. Burada bağımsız / özgür bir geliştirme ortamından bahsetmek mümkün.

Nest Neyi Vaad Ediyor?

Nest, Node.js üzerinde yer alan birçok server-side frameworkten birisi yine. Diğer farmeworklerde yaşanan test edilebilirlik, ölçeklenebilirlik ve kolayca maintain edilebilirlik (bakım yapılabilir) sorunlarının Nest ile yaşanmaması için hazır bir uygulama mimarisi sunuluyor.

Nest Nasıl Kurulur?

Basit olarak Nest için ben npx kullanmayı tercih ediyorum. Bu yazıda da öyle yapacağız.

npx @nestjs/cli new proje-adiniz

Bu sayede bir proje oluşturacaksınız. Sadece proje oluşurken bir prompt gelecek ve orada da npm ya da yarn seçeneceğini seçmelisiniz.

Yeni oluşturduğunuz projeyi çalıştırma işini de şöyle yapalım;

npm run start

Eğer hot-reload istiyorsanız da şöyle yapmalısınız;

npm run start:dev

Bunların tamamen package.json altında hazır şekilde geliyor zaten.

İlk Adımlar

İlk kurulumda aşağıdaki şekilde dosyalar gelecek;

src/
 - app.controller.ts
 - app.service.ts
 - app.module.ts
 - main.ts

Şimdi bu dosyalara bakalım

main.ts

Bu dosya uygulamanın ayağa kalktığı, bootstrapper dosya olarak kullanılıyor. Aşağıdaki kodda yer alan bootstrap fonksiyonunun async bir fonksiyon olduğuna dikkat etmelisiniz.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

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

Burada listen fonksiyonunun aldığı 3000 nolu port istediğiniz herhangi bir port olabilir. Tabii ki development içerisinde bu portun önemi yok ancak siz bu projeyi production’a alacaksanız bunun environment içerisinden geleceğini düşünmelisiniz.

Yeni bir nest application instance’ı oluşturmak için NestFactory class’ını kullanıyoruz. Bu class birkaç statik metoda sahip. Burada yer alan create() metodu ise bir uygulama objesi döner.

Bu arada platform seçimi yani Express ya da Fastify seçimi yapmak isterseniz create metodu şöyle değişmeli;

Express İçin

const app = await NestFactory.create<NestExpressApplication>(AppModule);

Fastify İçin

const app = await NestFactory.create<NestFastifyApplication>(AppModule);

app.module.ts

Bu dosya uygulamanın root modülü. İçine baktığımızda buna benzer bir kod yapısı göreceğiz.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Bu default kurulumda nest-cli tarafından yapılandırılıyor. Daha sonraları nest-cli kullanarak bir controller ya da service oluşturursanız da buraya otomatik ekleniyor.

app.controller.ts ve app.service.ts

Bu dosyalar örnek dosyalar olarak son kullanıcıya geliyor. Bu dosyaları silelim çünkü kendimiz nest-cli olmadan bunları oluşturacağız. Buna yavaş yavaş bakacağız.

Contollers ve Providers

Nest ile uygulama geliştirecekseniz controllers ve providers kavramlarını sık sık kullanacaksınız demektir.

Controllers Mekanizması

Kısacası bir controller, gelen istekleri karşılamaktır. Genellikle controller içerisinde birden fazla route yer alır. Nest içerisinde controller oluşturmak istiyorsanız sınıflar ve bunlara dair metadata verisini taşıyan decoratorler kullanılıyor.

Her controller başında @Controller() içeren bir decorator’e sahip. Eğer bu decorator parametre almazsa PrefixController şeklinde oluşturulan prefix’i route olarak ayarlar.

Bu ne demek? Örneğin, CustomersController adında bir classınız var bu decorator parametre almaz ise route değeriniz /customers olur. Ama siz buna gidip @Controller('musteriler') derseniz route /musteriler olur.

Controller içerisinde http verbleriyle ilişkilendirilecek şekilde kullanabileceğiniz decoratorler yer almaktadır. Bunlara da değineceğiz. Ama önce service mekanizmasına ve proje yapılandırmasına bakalım.

Providers Mantığı

Yukarıda gördüğümüz service dosyaları aslında birer providers görevi görüyor. Nest’te bunlar temel olarak bulunuyorlar.

Bir provider’ın temel amacı controller ya da farklı herhangi bir yere bağımlılıkları inject edebilmesinden geçiyor. Bir provider classtan oluşuyor ve @Injectable() decoratoru ile oluşturulmuştur.

Nest tarafından oluşturulan örneklere bakacak olursanız veri tabanı işlemlerinin bu servislerde gerçekleştirildiğini görebilirsiniz. Biz de bu yazı içerisinde buna benzer bir örnek göreceğiz. Ama öncelikle projemizi nasıl yapılandırıyoruz? Ona bir bakalım.

Proje Yapılandırması

Burada proje yapılandırması önemli. Bu yazı basit bir yazı olacağı için projenin çok uç olmasa da basit bir iskeleti olacak. Ben bu yazı için aşağıdaki gibi bir yapıya sahibim;

/src
  - controllers/
  - services/
  - models/

Az çok yapıyı anladınız sanıyorum. Tüm controllers dosyaları ayrı bir klasörde, services dosyaları ayrı bir klasörde ve model dosyaları ayrı bir klasörde yer alacak.

Ayrıca her controller ve provider için birer base.controller ve base.service adında dosya oluşturup genel bi exporter yapacağız. Önce bu dosyaları oluşturalım

controllers/base.controller.ts

export {
  // ... 
}

services/base.service.ts

export {
  // ... 
}

Evet bunlar bu kadar. Şimdi controller oluşturalım.

İlk Controllerımız

Oluşturacağımız controller’ın ismi HomeController olacak.

controllers/home.controller.ts

import { Controller, Get } from "@nestjs/common";
import { HomeService } from "src/services/base.service";
import HomeModel from "src/models/home.model";

@Controller()
export default class HomeController {
    constructor(private readonly homeService: HomeService) {}

    @Get()
    getHome(): HomeModel {
        return this.homeService.getHome();
    }
}

Bu kadar ancak bu haliyle oluşturulmamış provider ve modeller nedeniyle hata alacaksınız. Hadi devam edelim.

İlk Providerımız

Bu provider’ımızın adı HomeService olacak.

services/home.service.ts

import { Injectable } from "@nestjs/common";
import HomeModel from "src/models/home.model";

@Injectable()
export default class HomeService {
    getHome(): HomeModel {
        const homeModel = new HomeModel();

        homeModel.message = 'Welcome';

        return homeModel;
    }
}

Şu anda hala hata alıyoruz. Çünkü henüz bir modelimiz yok. Hadi onu da oluşturalım

İlk Model Dosyamız

Bu dosyamızın adı HomeModel olacak.

models/home.model.ts

export default class HomeModel {
    public message: string;
}

Evet ancak hala hata alıyoruz. Çünkü henüz base.controller.ts ve base.service.ts dosyamız controller ve provider classlarımızı export etmiyor. Gelin düzeltelim.

controllers/base.controller.ts

import HomeController from './home.controller';

export {
    HomeController
}

services/base.service.ts

import HomeService from './home.service'

export {
    HomeService
}

Şu anda çalışmasının önündeki tek engel app.module.ts dosyasının düzenlenmesi. Gelin düzenleyelim.

App Module Dosyasının Düzenlenmesi

Bu dosyaya hem controller hem de provider classlarımızı aşağıdaki şekilde çağıracağız.

app.module.ts

import { Module } from '@nestjs/common';
import { HomeController } from './controllers/base.controller';
import { HomeService } from './services/base.service';
@Module({
  imports: [],
  controllers: [HomeController],
  providers: [HomeService],
})
export class AppModule {}

Gördüğünüz gibi controllers ve providers parametre olarak kendi türlerinden array alıyor. Peki burada imports nedir?

Modüler Yapı

Belki de nest’in sağladığı en büyük avantaj uygulamayı modüllere bölmenize imkan tanıması. Bu yazıdaki tek bir modülden birden fazla olduğunu düşünün. Büyük çaplı uygulamalarda her modül bir feature olarak kullanılabilir.

Basit bir modül ağacı aşağıdaki şekilde oluşturulabilir;

src/
 - kullanicilar/
  - controllers/
  - services/
  - models/
  - kullanicilar.module.ts

Modül kodu ise şöyle olsun

kullanicilar.module.ts

import { Module } from '@nestjs/common';
import { KullanicilarController } from './base.controller';
import { KullanicilarService } from './base.service';

@Module({
  controllers: [KullanicilarController],
  providers: [KullanicilarService],
})
export class KullanicilarModule {}

Sonra bunu root module içerisine import edelim.

app.module.ts

import { Module } from '@nestjs/common';
import { KullanicilarModule } from './kullanicilar/kullanicilar.module';

@Module({
  imports: [KullanicilarModule],
})
export class AppModule {}

İşte bu uygulamanın daha kolay anlaşılabilir olmasını sağladı. Dilerseniz ilk örnekteki şekliyle bir proje yapısı kurabilirsiniz ya da bu haliyle uygulama uygulama bir proje yapısı da kurabilirsiniz. Bu yapı Django ile proje geliştirmiş kişilere hiç yabancı gelmeyecektir.

Hepsi bu kadar 🙂 Okuduğunuz için teşekkür ederim. Umarım faydalı olur.

GitHub Üzerinde Örnek Uygulama

Uygulamaya GitHub üzerinden ulaşmak için aşağıdaki bağlantıyı kullanabilirsiniz;

https://github.com/aligoren/nest-ornek

Kaynaklar