2024. 6. 7. 00:53ㆍ서버 프로그래밍
오랜만에 Node.js로 풀스택 개발을 하게 되었는데, Nest.js를 백엔드로 ReactJS를 프론트엔드로 사용하기로 했다. 먼저 Nest.js 프로젝트를 생성한 다음, frontend라는 폴더를 만들고 거기에 ReactJS 프로젝트를 생성했다. 따라서, Nest.js 백엔드에서 빌드된 ReactJS의 파일을 보여주도록 app.module.ts를 수정했다.
https://docs.nestjs.com/first-steps
app.module.ts
import { Module } from '@nestjs/common';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { HealthController } from './health.controller';
import { ReadinessController } from './readiness.controller';
@Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'frontend', 'build'),
exclude: ['/api*'],
}),
],
controllers: [AppController, HealthController, ReadinessController],
providers: [AppService],
})
export class AppModule {}
app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('api')
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
* app.module.ts에서 ServeStaticModule로 ReactJS의 build 폴더를 rootPath로 지정을 해도 계속 "Hello, World!"가 뜬다. app.controller.ts에서 AppController가 루트로 지정되어있기 때문이며 'api'로 변경해주자 해결되었다.
Implementing User Authentication in Nest.js
Securing Your Nest.js Application with Robust Authentication
User authentication is a critical component of server-side applications. Nest.js provides a straightforward way to implement authentication mechanisms, such as JWT (JSON Web Tokens), to secure your application.
// Example of a Nest.js authentication service using JWT
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(private jwtService: JwtService) {}
async validateUser(username: string, pass: string): Promise<any> {
// Logic to validate the user credentials
}
async login(user: any) {
const payload = { username: user.username, sub: user.userId };
return {
access_token: this.jwtService.sign(payload),
};
}
}
Managing Access Tokens and Environment Variables
Managing access tokens securely is essential for user authentication. Nest.js supports the use of environment variables, which can be stored in an .env file, to manage sensitive information such as secret keys for token generation.
// Using environment variables in Nest.js
import { config } from 'dotenv';
config();
// Accessing environment variables
const jwtSecret = process.env.JWT_SECRET;
https://www.dhiwise.com/post/mastering-server-side-rendering-with-nestjs-and-react
https://bewar-usman.medium.com/running-react-js-with-nest-js-together-4ea2fa5b779d
* React 프로젝트를 Typescript로 생성하고자 할때
npx create-react-app my-app --template typescript
https://create-react-app.dev/docs/adding-typescript/
Docker 설정 관련
https://www.tomray.dev/nestjs-docker-production
https://geshan.com.np/blog/2023/03/mongodb-docker-compose/
Cypress 관련 (React Typescript)
https://docs.cypress.io/guides/component-testing/react/overview
https://github.com/cypress-io/cypress-component-testing-apps/tree/main/react-cra5-ts
* 아래 레퍼런스들은 out date 되었거나 딱 맞지는 않지만 참고할만함
https://www.freecodecamp.org/news/cypress-for-end-to-end-testing-react-apps/
* GitLab 파이프라인에서 별도로 서버를 실행시키고, cypress로 e2e 테스트를 진행하고자 할때
Deploy backends as docker images in GitLab private registry
First you have to publish your docker images in the private registry of GitLab. You do this, because you now can reuse those images in another job. For this approach you need docker:dind. A simple example job to publish to a private registry on gitlab looks like:
before_script:
- echo -n $CI_JOB_TOKEN | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY
publish:image:docker:
stage: publish
image: docker
services:
- name: docker:dind
alias: docker
variables:
CI_DOCKER_NAME: ${CI_REGISTRY_IMAGE}/my-docker-image
script:
- docker pull $CI_REGISTRY_IMAGE || true
- docker build --pull --cache-from $CI_REGISTRY_IMAGE --tag $CI_DOCKER_NAME --file Dockerfile .
- docker push $CI_DOCKER_NAME
only:
- master
To see a real-world example, I have an example project that is public available.
Mimic your docker-compose.yml services in 1 job in the pipeline
Once you dockerized all backends and published the images on a private registry, you can start to mimic your docker-compose.yml with a GitLab job. A basic example:
test:e2e:
image: ubuntu:20.04
stage: test
services:
- name: postgres:12-alpine
alias: postgress
- name: mongo
alias: mongo
# my backend image
- name: registry.gitlab.com/[MY_GROUP]/my-docker-image
alias: server
script:
- curl http://server:3000 # expecting server exposes on port 3000, this should work
- curl http://mongo:270117 # should work
- curl http://postgress:5432 # should work!
Run the tests
Now everything is running in a single job in GitLab, you can simply start your front-end in detached mode and run cypress to test it. Example:
script:
- npm run start & # start in detached mode
- wait-on http://localhost:8080 # see: https://www.npmjs.com/package/wait-on
- cypress run # make sure cypress is available as well
https://stackoverflow.com/questions/66071016/how-to-setup-gitlab-ci-e2e-tests-using-multiple-dockers
https://docs.gitlab.com/runner/executors/docker.html#create-a-network-for-each-job
Connecting services
You can use inter-dependent services with complex jobs, like end-to-end tests where an external API needs to communicate with its own database.
For example, for an end-to-end test for a front-end application that uses an API, and where the API needs a database:
end-to-end-tests:
image: node:latest
services:
- name: selenium/standalone-firefox:${FIREFOX_VERSION}
alias: firefox
- name: registry.gitlab.com/organization/private-api:latest
alias: backend-api
- postgres:14.3
variables:
FF_NETWORK_PER_BUILD: 1
POSTGRES_PASSWORD: supersecretpassword
BACKEND_POSTGRES_HOST: postgres
script:
- npm install
- npm test
For this solution to work, you must use the networking mode that creates a new network for each job.
https://docs.gitlab.com/ee/ci/services/#connecting-services
* gitlab runner에서 cypress를 실행시키니 Xvfb가 없다며 에러가 뜬다. docker 이미지를 cypress 공식 이미지를 사용하는 것으로 해결했다.
Xvfb is an in-memory display server for a UNIX-like operating system, in your case Linux (Debian - 9.13). This is a system-level dependency that cypress assumes is installed beforehand. In the case of bitbucket env, this is not installed and hence this error. This is primarily used for Display (UI) in case of headed browsers while running cypress.
There are few easy workarounds here:
Installing the dependency manually: I won't suggest this as you might miss out few more dependencies unless you thoroughly look into all the dependencies.
Run headless browsers:: Headless browsers don't use Xvfb, so won't be facing this exact error but as I said in point 1, there can be other issues. Command will become cypress run --headless
Use Cypress provided Docker images (Suggested): I can see that you are using the node:14.15.4 image for this build. Instead of that, use a cypress provided official base images for whatever node version you want to run (Look for different node version in tags). If you look into the Dockerfile, you will see that they have taken an effort to install the dependencies needed to run Cypress inside the docker (You can see Xvfb there).
Also, look for already open issues when you get stuck in cypress-related issues, they have a very active and helpful issues section. I found this same issue in there: https://github.com/cypress-io/cypress/issues/14457
* 프론트엔드가 백엔드의 하위 폴더에 있다보니 lint 설정에서 삽질을 해야하는 경우가 생겼다.
https://andrebnassis.medium.com/setting-eslint-on-a-react-typescript-project-2021-1190a43ffba
https://dev.to/quizzes4u/how-to-set-up-eslint-and-prettier-in-react-typescript-5-project-2023-hd6