icon

프론트엔드 통합 (React, Angular)


 NestJS 백엔드와 프론트엔드 프레임워크(React 또는 Angular)의 통합은 강력하고 확장 가능한 풀스택 애플리케이션을 구축하는 데 필수적입니다.

 이 과정에서는 효율적인 데이터 흐름, 보안, 성능 최적화 등 다양한 측면을 고려해야 합니다.

아키텍처 및 워크플로우

  1. NestJS 백엔드 : RESTful API 또는 GraphQL 엔드포인트 제공
  2. 프론트엔드(React/Angular) : 사용자 인터페이스 및 상태 관리
  3. API 게이트웨이 : 필요한 경우 프론트엔드와 백엔드 사이에 위치
  4. 인증 서버 : JWT 또는 OAuth 기반 인증 처리

 워크플로우

  1. 프론트엔드에서 사용자 액션 발생
  2. 프론트엔드 서비스 레이어에서 API 요청
  3. NestJS 백엔드에서 요청 처리 및 응답
  4. 프론트엔드에서 응답 처리 및 상태 업데이트

프론트엔드 프로젝트 설정 및 연동

 React 프로젝트 설정

npx create-react-app my-app
cd my-app
npm install axios

 Angular 프로젝트 설정

ng new my-app
cd my-app
npm install @angular/common-http

API 통신 서비스 레이어 구현

 React에서 Axios를 사용한 API 서비스

import axios from 'axios';
 
const API_URL = 'http://localhost:3000/api';
 
export const getUsersService = {
  getAll: () => axios.get(`${API_URL}/users`),
  getById: (id) => axios.get(`${API_URL}/users/${id}`),
  create: (userData) => axios.post(`${API_URL}/users`, userData),
  update: (id, userData) => axios.put(`${API_URL}/users/${id}`, userData),
  delete: (id) => axios.delete(`${API_URL}/users/${id}`),
};

 Angular에서 HttpClient를 사용한 API 서비스

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
 
@Injectable({
  providedIn: 'root'
})
export class UserService {
  private API_URL = 'http://localhost:3000/api';
 
  constructor(private http: HttpClient) {}
 
  getAll(): Observable<any> {
    return this.http.get(`${this.API_URL}/users`);
  }
 
  getById(id: string): Observable<any> {
    return this.http.get(`${this.API_URL}/users/${id}`);
  }
 
  create(userData: any): Observable<any> {
    return this.http.post(`${this.API_URL}/users`, userData);
  }
 
  update(id: string, userData: any): Observable<any> {
    return this.http.put(`${this.API_URL}/users/${id}`, userData);
  }
 
  delete(id: string): Observable<any> {
    return this.http.delete(`${this.API_URL}/users/${id}`);
  }
}

상태 관리 전략

 React에서 Redux 사용

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { getUsersService } from '../services/userService';
 
export const fetchUsers = createAsyncThunk(
  'users/fetchUsers',
  async () => {
    const response = await getUsersService.getAll();
    return response.data;
  }
);
 
const usersSlice = createSlice({
  name: 'users',
  initialState: { entities: [], loading: 'idle' },
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(fetchUsers.fulfilled, (state, action) => {
      state.entities = action.payload;
      state.loading = 'idle';
    });
  },
});
 
export default usersSlice.reducer;

 Angular에서 NgRx 사용

import { createAction, props } from '@ngrx/store';
import { createEffect, Actions, ofType } from '@ngrx/effects';
import { UserService } from '../services/user.service';
import { map, mergeMap } from 'rxjs/operators';
 
export const loadUsers = createAction('[Users] Load Users');
export const loadUsersSuccess = createAction('[Users] Load Users Success', props<{ users: any[] }>());
 
export class UserEffects {
  loadUsers$ = createEffect(() => 
    this.actions$.pipe(
      ofType(loadUsers),
      mergeMap(() => this.userService.getAll().pipe(
        map(users => loadUsersSuccess({ users }))
      ))
    )
  );
 
  constructor(
    private actions$: Actions,
    private userService: UserService
  ) {}
}

인증 및 권한 부여

 JWT를 사용한 인증 구현

 React

import axios from 'axios';
 
const setAuthToken = token => {
  if (token) {
    axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
  } else {
    delete axios.defaults.headers.common['Authorization'];
  }
};
 
export const login = async (credentials) => {
  const response = await axios.post('/api/auth/login', credentials);
  const { token } = response.data;
  localStorage.setItem('token', token);
  setAuthToken(token);
  return response.data;
};
 
export const logout = () => {
  localStorage.removeItem('token');
  setAuthToken(null);
};

 Angular

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { tap } from 'rxjs/operators';
 
@Injectable({
  providedIn: 'root'
})
export class AuthService {
  constructor(private http: HttpClient) {}
 
  login(credentials: any) {
    return this.http.post('/api/auth/login', credentials).pipe(
      tap((response: any) => {
        localStorage.setItem('token', response.token);
      })
    );
  }
 
  logout() {
    localStorage.removeItem('token');
  }
 
  getToken() {
    return localStorage.getItem('token');
  }
}

실시간 기능 구현

 Socket.IO를 사용한 WebSocket 구현

 React

import io from 'socket.io-client';
 
const socket = io('http://localhost:3000');
 
socket.on('connect', () => {
  console.log('Connected to server');
});
 
socket.on('message', (data) => {
  console.log('Received message:', data);
});
 
export const sendMessage = (message) => {
  socket.emit('sendMessage', message);
};

 Angular

import { Injectable } from '@angular/core';
import { io } from 'socket.io-client';
import { Observable } from 'rxjs';
 
@Injectable({
  providedIn: 'root'
})
export class WebsocketService {
  private socket: any;
 
  constructor() {
    this.socket = io('http://localhost:3000');
  }
 
  listen(eventName: string) {
    return new Observable((subscriber) => {
      this.socket.on(eventName, (data) => {
        subscriber.next(data);
      });
    });
  }
 
  emit(eventName: string, data: any) {
    this.socket.emit(eventName, data);
  }
}

프론트엔드 라우팅

 React Router 사용

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
 
function App() {
  return (
    <Router>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/users" component={UserList} />
        <Route path="/user/:id" component={UserDetail} />
      </Switch>
    </Router>
  );
}

 Angular Router 사용

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'users', component: UserListComponent },
  { path: 'user/:id', component: UserDetailComponent }
];
 
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

성능 최적화

 React에서 Code Splitting 사용

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
 
const Home = lazy(() => import('./routes/Home'));
const UserList = lazy(() => import('./routes/UserList'));
 
function App() {
  return (
    <Router>
      <Suspense fallback={<div>Loading...</div>}>
        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/users" component={UserList} />
        </Switch>
      </Suspense>
    </Router>
  );
}

 Angular에서 Lazy Loading 사용

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'users', loadChildren: () => import('./users/users.module').then(m => m.UsersModule) }
];

테스팅 전략

 React 컴포넌트 테스트 (Jest + React Testing Library)

import React from 'react';
import { render, screen } from '@testing-library/react';
import UserList from './UserList';
 
test('renders user list', () => {
  render(<UserList users={[{ id: 1, name: 'John Doe' }]} />);
  const userElement = screen.getByText(/John Doe/i);
  expect(userElement).toBeInTheDocument();
});

 Angular 컴포넌트 테스트 (Jasmine + Karma)

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserListComponent } from './user-list.component';
 
describe('UserListComponent', () => {
  let component: UserListComponent;
  let fixture: ComponentFixture<UserListComponent>;
 
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ UserListComponent ]
    }).compileComponents();
  });
 
  beforeEach(() => {
    fixture = TestBed.createComponent(UserListComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });
 
  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Best Practices 및 주의사항

  1. 일관된 코드 스타일 및 구조 유지
  2. 환경 변수를 사용한 설정 관리
  3. 적절한 에러 처리 및 사용자 피드백 제공
  4. 보안 Best Practices 준수 (XSS 방지, CSRF 토큰 사용 등)
  5. 접근성(A11Y) 고려
  6. 반응형 디자인 구현
  7. 성능 모니터링 및 최적화
  8. 지속적인 통합 및 배포(CI/CD) 파이프라인 구축
  9. 프론트엔드와 백엔드 간 API 계약 준수
  10. 정기적인 의존성 업데이트 및 보안 패치 적용

 NestJS 백엔드와 프론트엔드(React 또는 Angular) 통합은 현대적이고 강력한 웹 애플리케이션을 구축하는 효과적인 방법입니다.

 이 접근 방식은 백엔드의 강력한 기능과 프론트엔드의 유연성을 결합하여 확장 가능하고 유지보수가 용이한 애플리케이션을 만들 수 있게 해줍니다.

 프로젝트 설정 단계에서는 각 프레임워크의 CLI 도구를 활용하여 기본 구조를 생성하고 필요한 의존성을 설치합니다.

 API 통신을 위한 서비스 레이어 구현은 프론트엔드와 백엔드 간의 데이터 흐름을 관리하는 핵심 요소입니다.

 React에서는 Axios, Angular에서는 HttpClient를 사용하여 효율적인 HTTP 요청을 구현할 수 있습니다.

 상태 관리는 복잡한 애플리케이션에서 특히 중요합니다.

 Redux(React) 또는 NgRx(Angular)와 같은 상태 관리 라이브러리를 사용하면 애플리케이션의 상태를 예측 가능하고 관리하기 쉬운 방식으로 유지할 수 있습니다.

 이러한 라이브러리는 NestJS 백엔드와의 데이터 동기화를 효율적으로 처리할 수 있게 해줍니다.