Skip to main content

4 posts tagged with "typescript"

View All Tags

· 2 min read
Luc Duong

Có thực sự mọi dự án đều phải có?

Nghe có vẻ là đao thua búa lớn, nhưng thực sự là cần phải có.

Ở một số góc nhìn khác thì tất cả những gì mình nói ở đây đều không cần tới và gần như không dự án nào có cả. Vậy tại sao lại cần? Cần như thế nào?

Thực sự là có. Bất kể ban đang làm dự án một mình, làm việc với team, dự án mã nguồn mở, hay đang làm ở một công ty thì những dự án phần mềm (Mobile App, WebApp, ...) đều có những điều cần phải được quan tâm:

  • Code standard là gì? Những rule nào được sử dụng trong dự án, code thế nào là clean, thế nào là không clean?
  • Development như thế nào? Setup environment ra làm sao?
  • Commit message đã clean chưa?
  • Code có bug không? Có smell nào không? Có bị trùng với code của người khác không?
  • Làm sao để bump một version? Major, minor, hay patch?
  • Có cần review PR không?
  • Làm sao để triển khai dự án trên server và làm sao để rollback khi có lỗi?

Có hàng tá vấn đề khác liên quan đến technical mà chúng ta cần phải quan tâm

...

· 9 min read
Luc Duong

Tổng quan

Sau bài viết trước, tôi đã giới thiệu về cách tôi viết package @ltv/env và cách tôi sử dụng nó trong dự án của mình. Trong bài viết này, tôi sẽ giới thiệu về cách tôi optimize package này để nó có thể sử dụng được trong nhiều dự án hơn và phù hợp với TypeScript hơn.

Các vấn đề cần giải quyết

1. Không phải lúc nào cũng sử dụng dotenv

info

dotenv là một công cụ giúp ta quản lý các biến môi trường trong ứng dụng bằng cách lưu trữ chúng trong một tệp định dạng plain text, sau đó đọc tệp này và đặt các biến môi trường tương ứng khi chạy ứng dụng.

Tuy nhiên, khi sử dụng dotenv ở production, có một số rủi ro tiềm ẩn như sau:

  • Bảo mật: Nếu tệp .env chứa thông tin nhạy cảm như mật khẩu hoặc khóa bí mật, nó có thể bị lộ khi triển khai ứng dụng ở production.

  • Hiệu suất: Đọc tệp .env có thể làm chậm hiệu suất của ứng dụng, đặc biệt là khi có nhiều biến môi trường.

  • Quản lý biến môi trường: Sử dụng dotenv có thể làm cho việc quản lý các biến môi trường trở nên phức tạp hơn, đặc biệt là khi có nhiều môi trường (ví dụ: staging, production, development).

Trong môi trường production, thay vì sử dụng dotenv, nên sử dụng các biến môi trường được cấu hình trực tiếp trên server. Điều này giúp đảm bảo bảo mật và hiệu suất, và giúp quản lý biến môi trường dễ dàng hơn.

Ở phiên bản trước của package này tôi có sử dụng dotenv để load các biến môi trường. Tuy nhiên, sau khi sử dụng trong một số dự án, tôi đã nhận ra rằng dotenv không phải lúc nào cũng phù hợp với mọi dự án. Đặc biệt là khi dự án của bạn sẽ được deploy lên môi trường production, dotenv sẽ không phù hợp với môi trường này.

Thực ra cách mà dotenv hoạt động là rất đơn giản, nó chỉ là một module đơn giản, nó sẽ đọc tệp .env và đặt các biến môi trường tương ứng. Sau khi load các biến môi trường, nó sẽ thêm process.env vào require.cache để các module sau có thể sử dụng được.

Nhưng khi sử dụng dotenv trong môi trường production, nó sẽ làm chậm hiệu suất của ứng dụng, đặc biệt là khi có nhiều biến môi trường. Điều này là không mong muốn, vì nó sẽ làm chậm hiệu suất của ứng dụng.

Trong môi trường production, nên sử dụng các biến môi trường được cấu hình trực tiếp trên server. Điều này giúp đảm bảo bảo mật và hiệu suất, và giúp quản lý biến môi trường dễ dàng hơn.

2. Nếu env không được set thì type của env sẽ là gì?

Hãy xem ví dụ sau:

import env from '@ltv/env'

const PORT = env.int('PORT')

Trường hợp này, chúng ta đang muốn lấy PORT từ environment, tuy nhiên chúng ta không set default value cho PORT, nên nếu PORT không được set thì env.int('PORT') sẽ trả về undefined. Điều này sẽ gây ra lỗi khi chúng ta sử dụng PORT. VD:

import env from '@ltv/env'

const PORT = env.int('PORT')

app.listen(PORT)

Kiểu dữ liệu của PORTnumber, nhưng

Ở đây, nếu PORT không được set thì app.listen(PORT) sẽ gây ra lỗi.

Tuy nhiên, điều chúng ta mong muốn ở đây không phải là sau khi chạy thì mới biết lỗi, mà là ngay khi development, chúng ta có thể biết được lỗi tiềm ẩn ở đây là gì.

Chính vì vậy, chúng ta cần phải mô tả rõ ràng kiểu dữ liệu của env để khi chúng ta sử dụng env mà không set default value thì nó sẽ báo lỗi ngay lập tức.

-> Ở trường hợp trên, kiểu dữ liệu của PORT sẽ là number | undefined. Điều này sẽ giúp chúng ta biết được nếu PORT không được set thì app.listen(PORT) sẽ gây ra lỗi. (Thực ra là PORT khả năng sẽ là undefined).

-> Tôi cần sửa lại: env.int('PORT') sẽ trả về number | undefined thay vì number.

Lúc này, vấn đề lại phát sinh, nếu tôi set default value cho PORT thì kiểu dữ liệu của PORT không thể là number | undefined nữa, vì nó sẽ luôn luôn là number.

Vậy làm sao để tôi có thể mô tả rõ ràng kiểu dữ liệu của env để khi chúng ta sử dụng env mà không set default value thì nó sẽ báo lỗi ngay lập tức, và khi set default value thì nó sẽ luôn luôn là kiểu dữ liệu đó?

3. Sử dụng nhiều dependencies quá

Mục đích của ứng dụng rất đơn giản là để load các biến môi trường, nhưng tôi lại sử dụng nhiều dependencies quá.

Tôi sử dụng lodash để check xem fields có tồn tại trong object hay không, hoặc merge object cũng dùng lodash, nhưng tôi thấy rằng lodash quá nặng, nó có nhiều tính năng không cần thiết cho package này. Vì vậy, tôi đã thay thế lodash.haslodash.merge bằng cách viết lại bằng tay.

VD: lodash.has:

/**
* Checks if `key` is a direct property of `object`.
*
* @param {Object} object The object to query.
* @param {string} key The key to check.
* @returns {boolean} Returns `true` if `key` exists, else `false`.
* @example
*
* const object = { 'a': { 'b': 2 } }
* const other = create({ 'a': create({ 'b': 2 }) })
*
* has(object, 'a')
* // => true
*
* has(other, 'a')
* // => false
*/
export function has<T>(object: T, key: PropertyKey): boolean {
return object != null && hasOwnProperty.call(object, key)
}

Hoặc lodash.trim:

/**
* Removes leading and trailing whitespace or specified characters from `string`.
*
* @param {string} [string=''] The string to trim.
* @param {string} [chars=whitespace] The characters to trim.
* @returns {string} Returns the trimmed string.
* @example
*
* trim(' abc ')
* // => 'abc'
*
* trim('-_-abc-_-', '_-')
* // => 'abc'
*/
export function trim(str: string, chars?: string) {
if (str && chars === undefined) {
return str.trim()
}
if (!str || !chars) {
return str || ''
}
const strSymbols = stringToArray(str)
const chrSymbols = stringToArray(chars)

let start = 0
let end = strSymbols.length - 1
while (chrSymbols.includes(str[start])) {
start++
}
while (chrSymbols.includes(str[end])) {
end--
}
return strSymbols.slice(start, end + 1).join('')
}

Bắt tay vào refactor code và upgarde lên version mới

1. Remove dotenvlodash dependency

Quá đơn giản, chỉ cần xóa ra khỏi package.jsonyarn.lock là xong. :D

Cách khác là chạy lệnh yarn remove dotenv lodash để xóa cả 2 dependencies này.

2. Refactor code

Phần số 1 chỉ là làm màu cho có chứ phần 2 này mới là quan trọng. Phần này sẽ tập trung giải quyết vấn đề làm sao để có thể mô tả rõ ràng kiểu dữ liệu của env để khi chúng ta sử dụng env mà không set default value thì nó sẽ báo lỗi ngay lập tức, và khi set default value thì nó sẽ luôn luôn là kiểu dữ liệu đó.

Trước khi refactor, function như thế nào?

string<string | undefined>(key: string, defaultValue?: string) {
const rtnValue = has(process.env, key) ? process.env[key] : defaultValue
return rtnValue
},

Function khá đơn giản. Nhận vào keydefaultValue (optional), nếu key tồn tại trong process.env thì trả về process.env[key], còn không thì trả về defaultValue.

Và type của rtnValuestring | undefined.

Như mình đã mô tả ở trên, giá trị trả về luôn là string hoặc undefined, mặc dù có set default value hay không. Điều này khá bất tiện khi sử dụng, khi ta luôn phải cast kiểu dữ liệu trước khi sử dụng. VD:

const host = env.int('HOST', 'localhost') as string

Mặc dù biết rất rõ nếu HOST không được set thì nó sẽ trả về localhost, nhưng vẫn phải cast kiểu dữ liệu.

Nếu nhìn kỹ hơn, thì ta thấy giá trị trả về của rtnValue sẽ phụ thuộc vào giá trị của defaultValue. Nếu defaultValueundefined thì rtnValue sẽ là string | undefined, còn nếu defaultValuestring thì rtnValue sẽ là string.

Khá là hợp lý, có nghĩa là bây giờ chúng ta chỉ cần kiểm tra xem defaultValue có được set hay không, nếu được set thì rtnValue sẽ luôn luôn là string, còn không thì rtnValue sẽ là string | undefined.

Tuy nhiên, làm sao mà kiểm tra điều kiện cho type được? Vì type không phải là một biến, nó chỉ là một mô tả cho biến. Vậy làm sao để mô tả được điều kiện cho type?

  • Đầu tiên làm sao để set return type của function là string hoặc string | undefined?

Để làm được điều này, chúng ta sẽ sử dụng conditional type của Typescript.

export type StringOrUndefined<T extends undefined | string> = T extends string ? string : string | undefined

StringOrUndefined là một conditional type. Nó sẽ trả về string nếu Tstring, còn không thì trả về undefined.

Vậy làm sao để mô tả được điều kiện cho type của rtnValue?

function string<R extends undefined | string>(key: string, defaultValue?: R) {
const rtnValue = has(process.env, key) ? process.env[key] : defaultValue
return rtnValue as StringOrUndefined<R>
},
  • R extends undefined | string Lúc này R là một generic type, nó có thể là undefined hoặc string.
  • defaultValue?: R Lúc này defaultValue có thể là undefined hoặc string.
  • StringOrUndefined<R> Như đã mô tả ở trên, StringOrUndefined sẽ trả về string nếu Rundefined, còn không thì trả về string | undefined.

Vậy là chúng ta đã có thể mô tả được type của rtnValuestring hoặc string | undefined tùy thuộc vào defaultValue được set hay không.

Ví dụ:

const host = env.string('HOST', 'localhost') // string
const user = env.string('USER') // string | undefined

Kết thúc

Chúng ta đã hoàn thành việc refactor code và upgrade lên version mới. Bây giờ chúng ta có thể sử dụng env mà không cần cast kiểu dữ liệu nữa.

yarn add @ltv/env

Sử dụng:

import env from '@ltv/env'

const host = env.string('HOST', 'localhost')
const port = env.int('PORT', 3000)
const isProduction = env.bool('NODE_ENV', false)

console.log(host, port, isProduction)

Bài viết hôm nay khá lủng củng, mình sẽ cố gắng viết những bài viết có chất lượng hơn. Cảm ơn các bạn đã đọc.

· 9 min read
Luc Duong

Tổng quan

Chả là việc viết code NodeJS nó cũng rối ren và phức tạp quá, với lại nhiều dự án dùng đi dùng lại những đoạn code giống nhau, utils giống nhau, core giống nhau, ... thế nên là phải đóng gói và đẩy nó lên đâu đó rồi mỗi khi cần sẽ lấy về dùng.

  • package: Cái này là tập hợp những đoạn code mà sẽ sử dụng nhiều lần ở nhiều nơi khác nhau trong một hoặc nhiều dự án. VD: lodash
  • Nơi nào đó: Ở đây có nghĩa là npmjs.org hay còn gọi là npm package registry
  • Đóng gói: Nói thì sang, chớ thật ra tôi ở trong thư mục của cái packge cần publish, tôi chạy lệnh npm publish rồi npm nó tự khắc nén lại thành file có đuôi .tar.gz rồi nó đẩy lên trên npm packge registry giúp tôi
  • Lấy về dùng: Có nghĩa là npm i -S package_name hoặc yarn add package_name đó

Bắt đầu như thế nào

Chắc là phải nói về cái luồng thì nó hợp lý hơn nhẩy:

  • Create project (Tạo thư mục tên package): mk -p package_name
  • init nodejs project yarn init
  • Install mấy package cần thiết cho cái đống này
  • Viết code gì đó (thường là các đồng chí hay copy từ cái mình đã viết rồi, quăng qua đây)
  • Viết Unit Test: Bởi vì là mình sẽ dùng đi dùng lại nhiều ở nhiều nơi khác nhau, nên cần phải đảm bảo mọi code mình viết ra là dùng được và không bị thừa thãi. Và quan trọng hơn là chạy được, không bị sai =))
  • Bundle: Là cái mọe gì vậy? Đơn giản là việc mình gộp các code mình viết ra thành 1 hoặc nhiều file rồi mang ra một thư mục khác (VD: dist)
    • Nếu dùng babel thì bắt buộc phải transpile nó ra dạng javascript thuần để có thể sử dụng được ở chỗ khác
    • Nếu dùng TypeScript thì dĩ nhiên là cũng phải transpile nó ra javascript thuần để có thể sử dụng được ở chỗ khác luôn
    • Nếu dùng Javascript mà muốn bundle lại thì cũng chơi luôn.
  • Đóng gói và publish: Gần như chả bao giờ cần phải quan tâm vì nó được support bởi npm tool hết rồi. :D

Bắt tay vào làm

Published Package - Anh em vào cái này để check source demo nhá. Thực tế là đang dùng luôn

Init project

Tối là tôi đang để project ở cái đường dẫn này. Anh em có thể để đâu cũng được luôn.

mkdir -p ~/ws/ltv/demo-env

Rồi, giờ vô init

cd ~/ws/ltv/demo-env
yarn init

Mặt mũi sau đó nó thế này:

❯ yarn init
yarn init v1.22.10
question name (demo-env):
question version (1.0.0): 0.1.0
question description: Demo safty environment parser
question entry point (index.js):
question repository url: https://github.com/ltv/demo-env.git
question author: Luc <luc@ltv.vn>
question license (MIT):
question private:
success Saved package.json
✨ Done in 65.90s.

Đấy. Check trong thư mục thì các đồng chí kiếm được file package.json

Dùng cái gì?

  • Mình muốn strongly type nên mình quyết định dùng TypeScript cho nó dễ viết. :D
  • Sau khi viết bằng TypeScript mình sẽ build ra javascript và đóng gói cái đống javascript mang đi
  • Vậy giờ dùng những package gì để hỗ trợ việc viết code bằng TypeScript nhẩy
Package nameversionreason
typescript4.2.3Khắc khỏi cần giải thích
ts-node9.1.1Bởi vì là mình sẽ dùng cái đống ngày ở server side nên mình dùng ts-node để có những api cần thiết
@types/node14.14.35Cái này là để có type của nodejs nè

Cơ bản vậy là đủ rồi. Chả cần gì nhiều hơn.

  • Nói thì nói vậy. Chớ muốn viết code tốt thì mình cần phải có unit test, nên mình quyết định dùng jest. Với kinh nghiệm của mình thì jest is the best.
Package nameversionreason
ts-jest26.5.4Dùng TypeScript nên cần thêm thằng này để hỗ trợ transpile
jest26.6.3Phải có là đương nhiên
jest-config26.6.3Hỗ trợ cho việc configuration
@types/jest26.0.21Cái này là type của jest đây
  • Đấy, thế là test ok rồi nhá. Giờ tính đến chuyện lint code, để đảm bảo mình code theo đúng cái chuẩn nào đó, chứ code linh tinh riết rồi không biết mình code thế nào thì bỏ mọe. Vậy là mình dùng eslint
Package nameversionreason
eslint7.22.0Để lint code nha
eslint-config-prettier8.1.0Dùng chung với prettier - ông nôi Microsoft recommend đây
eslint-plugin-jest24.3.2Cái này là lint mấy cái jest unit test
@typescript-eslint/eslint-plugin4.18.0eslint cho TypeScript
@typescript-eslint/parser4.18.0eslint parser cho TypeScript
  • Nghe có vẻ là ổn rồi đấy, nào là test code, nào là check code. Nhưng mà méo. Vẫn còn nhé. Ngoài mấy việc trên, mình cần phải commit code với message chuẩn chỉnh. VD. Các đồng chí hay chơi trò: commit code hoặc fix cái gì đó hoặc refactor, ... Nhìn vô méo muốn review code huống chi là merge.

  • Đến khúc này chắc các đồng chí sẽ nói: Thôi thôi, ông im mẹ nó mồm đi, vậy giờ nó thế nào mới đúng?

  • Ờ thì các đồng chí hỏi tôi sẽ trả lời. Nó nhìn na ná thế này đây:

2021-03-28 09:12 +0700 Luc o [main] {origin/main} feat: add dotenv for loading process.env
2021-03-21 20:04 +0700 Luc o <v1.1.0> v1.1.0
2021-03-21 20:03 +0700 Luc o v1.0.0
2021-03-21 20:02 +0700 Luc o <v0.0.0> 0.0.0
2021-03-21 19:46 +0700 Luc o ci: add NPM_TOKEN, NPM_USERNAME, NPM_EMAIL for authenticating npmjs registry
2021-03-21 19:37 +0700 Luc o <v1.0.0> ci: add GITHUB_TOKEN
2021-03-21 18:55 +0700 Luc o ci: fixed: Failed all unit test
2021-03-21 18:48 +0700 Luc o refactor: change npm registry
2021-03-21 18:39 +0700 Luc o ci: seperate test & release steps
2021-03-21 18:26 +0700 Luc o chore: change github https to ssh
2021-03-21 18:24 +0700 Luc o chore: move release config to package.json
2021-03-21 18:19 +0700 Luc o feat: nodeJs Environment Utils
  • Vậy chớ giờ làm sao? Làm cái mọe gì nữa? Install cái đống ni vô:
Package nameversionreason
commitizen4.2.3
cz-conventional-changelog3.3.0
@commitlint/cli12.0.1
@commitlint/config-conventional12.0.1
  • Và khi commit, các đồng chí đừng dùng git commit nữa nha. Dùng git-cz hộ tôi phát.
  • Nếu thêm vào scripts trong file package.json thì có thể dùng thế này:

package.json

"scripts": {
"commit": "git-cz"
}

Execute command

yarn commit

Rồi rồi, nó sẽ trông thế này:

Alt text

Đấy, nhìn thấy sướng hơn chưa. Giờ thì tha hồ mà chọn kiểu commit, rồi điền thông tin vô nhá.

Alt text

  • Rồi ok. Còn gì nữa không? Còn. Các bố có linting rồi, nhưng mà trước khi commit, các bố quên thì sao? Là một đống 💩 trong code chứ mọe gì nữa. Ấy vậy nên chúng nó nghĩ ra cái lint-staged để đảm bảo các bố nhớ việc lint trước khi commit
Package nameversionreason
lint-staged10.5.4Ở trên kìa
  • Mọe. Dài quá bác ơi. Thì dài thật, nhưng mà, đi tiếp tí nữa đi. Nhá. Còn tí xíu nữa thôi. Vì các bố làm các bố còn phải format code cho nó đẹp tí xíu, nên tôi recommend các bố add thêm cái này nha:
Package nameversionreason
prettier2.2.1
  • Xém hết rồi, ráng lên xíu nhá. Gần tới bước cuối. Có vẻ mọi thứ đã gần như là ok. Giờ đến khúc release rồi này. Các đồng chí sẽ phải thắc mắc đặt tên version như thế nào. Ok. Có một cái tên là semantic-release =)). Install tiếp nhá. Ở bước cuối cùng của bài viết này tôi sẽ lôi các bố vô để giải thích.
Package nameversionreason
semantic-release17.4.2
  • Rồi. Đến cuối rồi. Khi mà các bố push code lên repo, các bố nên chạy cái unit test rồi check xem các bố viết được bao nhiêu unit test, cover được bao nhiêu code viết ra, nhìn vào đó, người ta sẽ quyết định có nên dùng package của bố viết không. Nói thì nói là người ta chớ bản thân các bố cũng tin tưởng mà dùng. Vậy nên tôi recommend các bố dùng cái này nha: ĐỂ PUSH VÀ LƯU VÀ SHARE COVERAGE REPORT. Nhá.
Package nameversionreason
coveralls3.1.0

Ban đầu tính là add thêm cái link cho từng package để các đồng chí xem, nhưng mà nghĩ lại. Ăn sẵn nhiều quá nó hư người. Nên các đồng chí chịu khó chép cái tên package rồi mang lên Google search hộ tôi phát nhé. Linh động lên.

Rồi rồi. Phần 1 kết thúc tại đây. Tôi sẽ lại tranh thủ viết tiếp.

Hóng hộ tôi cái nha. Tôi cũng sẽ quay lại việc làm Video trên Youtube. Chứ mệt quá bỏ cả năm rồi. Bài viết ngày tôi viết lúc rảnh, khi ngồi trên xe đi làm. =))

Còn video thì sẽ làm khi sáng sớm thức giấc.

Các đồng chí ủng hộ tôi tiếp nha.

· 5 min read
Luc Duong
info

Lâu rồi không viết bài gì cả nên nay mình viết một bài xàm xàm xíu. Cũng có hữu ích cho một số bạn làm NodeJS.

Tổng quan

process.env là một biến toàn cục trong NodeJS được set tự động khi app nodejs start. Có thể hình dung thế này: Khi ta set biến môi trường cho hệ điều hành export NODE_PORT=3000 thì ta sẽ lấy được thông tin đó ở process.env, nó sẽ là: process.env.NODE_PORT

Biến môi trường này có quan trọng không?

Quan trọng vãi nồi ra. Để mình ví dụ cái nhẩy. Giả sử app NodeJS của mình có kết nối DB và thông tin nó thế này:

database_host=127.0.0.1
database_user=root
database_port=3306
database_pass=SecurePass

Khi dùng ở local, thì muốn define sao cũng được, set cứng cũng được luôn. Cơ mà khi các bố mang deploy lên trên server / production / dev / hay đại loại là máy thằng khác thì nó ra sao? Nếu mà set cứng thì sẽ phải vào sửa thông tin trong code. Mỗi lần như vậy thì sẽ phải sửa. Nó phiền vcl.

Nếu chỉ có 4 cái bên trên thì chắc là cũng chả vấn đề gì nhẩy nhưng mà thường thì nó sẽ nhiều hơn như vậy rất nhiều, có thể đến vài chục cái.

Chính vì thế những thông tin có thể / cần phải thay đổi khi thay đổi môi trường chạy thì nên (phải) để ở biến môi trường.

Dùng nó như thế nào?

Cơ bản là trước khi start cái app NodeJS lên thì phải set ENV cho hệ điều hành. Nhưng mà nó phiền lắm, nếu có khoảng vài chục cái env khác nhau mà các bố mang đi set trước khi start thì đúng là bệnh vãi. Vậy nên không có ai làm như vậy cả. Thường là nó dùng cái nào đó để set tự động. Nhưng trong trường hợp này thì mình không nên set vào biến môi trường của OS vì nếu mình mà dev vài cái app khác nhau thì nó sẽ bị conflict env. Cách tốt nhất là set vào process.env của mỗi app riêng biệt.

Đâu đó nó thế này:

process.env.DATABASE_PORT=3000

Với NodeJS app thì ta có 1 cái package khá nổi tiếng để set env cho biến process.env đó là dotenv. Và việc của mình là mang hết env vào trong một file. VD: .env / local.env chẳng hạn.

VD: local.env

DATABASE_HOST=127.0.0.1
DATABASE_PORT=3306
DATABASE_USER=root
DATABASE_PASS=SecurePass

Rồi trong code mình load thế này (app.js / index.js):

require('dotenv').config({ path: 'local.env' })

Sau đó thì cái biến global process.env sẽ có toàn bộ những env mà mình đã define ở file local.env và có thể dùng bất cứ đâu trong app NodeJS.

VD:

const connection = new Connection({
host: process.env.DATABASE_HOST,
port: process.env.DATABASE_PORT,
user: process.env.DATABASE_USER,
pass: process.env.DATABASE_PASS
});

Dùng sao cho ngon

Câu chuyện ở trên chắc là các đồng chí đã hiểu rồi nhẩy. Vấn đề bây giờ là dùng sao cho ngon.

Quay lại câu chuyện ở trên, nếu các bố có file env thì không sao, nhưng nếu không có thì cũng vỡ mặt. Cái đống process.env sẽ không có gì. Vậy giờ nếu không có env thì sẽ xử lý sao?

Điều đương nhiên là phải check null rồi set default env chớ mọe gì nữa.

const connection = new Connection({
host: process.env.DATABASE_HOST || 'localhost',
port: process.env.DATABASE_PORT || 3306,
user: process.env.DATABASE_USER || 'root',
pass: process.env.DATABASE_PASS || 'noPASSon'
});

Ờ. Có vẻ là giải quyết vấn đề rồi đấy. Nhưng nếu đi sâu hơn 1 tí, thì tất cả các env đều có value type là string. Có nghĩa là cái thằng process.env.DATABASE_PORT sẽ là: '3306' chứ không phải là 3306. Mà cái mình muốn thì nó phải là 3306.

Tương tự, trường hợp oái oăm hơn nó có thể xảy đến thế này:

Các ông có:

DATABASE_SSL=false

Lúc dùng thì là:

const connection = new Connection({
...otherConfigs,
ssl: process.env.DATABASE_SSL || true
});

Các bố để ý nhé. process.env.DATABASE_SSL là string 'false' Mà string khác empty khác undefined và khác null thì có nghĩa là ssl=true.

Chết mọe rồi. Các bố đang muốn set ssl=false mà. Chuyện phức tạp hơn rồi phải không?

Vậy có nghĩa là phát sinh thêm một chuyện nữa. Các bố bắt buộc phải ép kiểu từ string về kiểu chính xác muốn dùng. Chốt lại có 2 chuyện cần làm:

  • Check null
  • Ép kiểu

Vậy túm cái váy lại là dùng thế nào cho gọn và ngon?

Để cho ngắn gọn thì các đồng chí install thằng này:

yarn add @ltv/env # npm i -S @ltv/env

Và dùng thôi:

const env = require('@ltv/env')
// Or can use with import
import env from '@ltv/env'

const dbPort = env.int('DATABASE_PORT', 3000)
# or string
const dbHost = env('DATABASE_HOST', 'localhost')
# or bool
const useSSL = env.bool('DATABASE_SSL', false)

# First arg is VARIABLE NAME
# Second arg is DEFAULT VALUE

Sử dụng ở ví dụ trên thì nhìn nó thế này:

const connection = new Connection({
host: env('DATABASE_HOST', 'localhost'),
port: env.int('DATABASE_PORT', 3306),
user: env('DATABASE_USER', 'root'),
pass: env('DATABASE_PASS', 'noPASSon)',
ssl: env.bool('DATABASE_SSL')
});

Trong bài viết tới chúng ta sẽ tìm hiểu xem viết cái lib này ra làm sao sau nhé. Một số bạn quen với NodeJS, mọi người có thể vào: https://github.com/ltv/env