- NextJS
- Firebase
실시간 채팅 애플리케이션 구현(1)
Nextron + Firebase(Firestore)를 이용한 실시간 채팅 Desktop앱 개발
Intro
Firebase에서 제공하는 인증과 Firestore의 실시간 데이터 동기화 기능을 사용하여
실시간 채팅 Desktop앱을 한 번 만들어 봅니다
구현전략
🤨 데스트탑 앱을 만들어야 하니 어떤 프로그램을 쓰면 좋을까
NextJS와 Electron을 결합한 Nextron이란 라이브러리로 환경을 구성해보자
😎 백엔드 서버를 따로 두지 않고 Firebase에서 지원하는 인증을 사용해보자
Firebase 콘솔에서 새로운 프로젝트를 생성한 후 config를 프로젝트에 세팅한다
😲 Firebase 인증을 적용하고 서버사이드에서 접근을 제한 해보자
- 1. 계정 생성 페이지를 생성하고 createUserWithEmailAndPassword 함수로 계정을 만든다
- 2. 로그인 페이지를 생성하고 signInWithEmailAndPassword 함수로 로그인을 한다
- 3. 토큰 관리를 위해 context를 생성한 후 onAuthStateChanged 함수로 유저의 로그인 여부를 판별한 뒤 쿠키에 저장한다
- 4. ServerSideProps를 사용하여 인증 상태를 판별한 뒤 성공 시 채팅 리스트로 실패 시 로그인 페이지로 보낸다 😏 채팅리스트와 각 채팅방에 입장하기 위한 라우터 처리는 이렇게 해보자 채팅방 입장은 pages에 chat/[id].tsx로 즉, 채팅방의 라우터 id로 접근하도록 한다(/chat/8796) 🙄 각 채팅방에 입장 시 입력된 메시지를 어떻게 보여주면 될까
- 1. useFirestoreQuery로 데이터베이스에 추가된 컬렉션의 메시지를 orderBy로 내림순으로 가져온다
- 2. 채팅 내용을 입력하고 firestore의 collection과 add 함수를 이용하여 메시지를 조회하고 추가한다
- 3. onSnapshot을 사용하여 채팅을 입력하기 전 쿼리와 입력 후 쿼리를 비교하여 채팅 내용을 업데이트 한다
환경구성
Core Defendencies
- - typescript 4.8.4
- - react 18.2.0
- - react-dom 18.2.0
- - next 13.1.2
- - nextron 8.4.0
- - electron 21.3.3
- - firebase 9.15.0
Install
우선 데스크탑앱을 만들어야 하기에 넥스트론 라이브러리를 사용하겠다
넥스트론 보일러플레이트는 Nextron 깃헙에 옵션별로 자세히 설명되어있다
npx create-nextron-app 프로젝트명
or
yarn create nextron-app 프로젝트명
구조
프로젝트가 설치된 후 폴더를 확인해보면 아래와 같은 구조를 띄운다
- - background.ts : 데스크탑에 표시되는 앱 상태를 설정하는 파일
- - electron-buillder.yml : 배포되는 앱의 옵션을 설정하는 파일
// background.ts
import { app } from 'electron';
import serve from 'electron-serve';
import { createWindow } from './helpers';
const isProd: boolean = process.env.NODE_ENV === 'production';
if (isProd) {
serve({ directory: 'app' });
} else {
app.setPath('userData', `${app.getPath('userData')} (development)`);
}
(async () => {
await app.whenReady();
const mainWindow = createWindow('main', {
width: 1000,
height: 600,
});
if (isProd) {
await mainWindow.loadURL('app://./index.html');
} else {
const port = process.argv[2];
await mainWindow.loadURL(`http://localhost:${port}/`);
mainWindow.webContents.openDevTools();
}
})();
app.on('window-all-closed', () => {
app.quit();
});
background.ts 파일은 데스크탑에서 앱을 실행할 시 적용될 옵션과 진입 경로 등을 설정할 수 있다
// package.json
"scripts": {
"dev": "nextron",
"build": "nextron build",
"postinstall": "electron-builder install-app-deps"
},
완료되었다면 package.json에 "dev" 스크립트를 실행하고 정상적으로 서버가 띄워진다면 환경구성은 이렇게 마무리된다
Firebase
이제 Firebase를 프로젝트에 세팅해줄건데
먼저 Firebase에 접속하고 콘솔로 이동한 뒤 프로젝트를 생성한다
프로젝트를 생성하면 apiKey를 포함한 config 객체를 주는데 이 정보로 앱에 연결하게 된다
const firebaseConfig = {
apiKey: XXXXXX,
authDomain: 'nextron-chat-app.firebaseapp.com',
projectId: 'nextron-chat-app',
storageBucket: 'nextron-chat-app.appspot.com',
messagingSenderId: XXXXXX,
appId: XXXXXX,
measurementId: XXXXXX,
}
이제 터미널을 열고 firebase를 설치해준다
yarn add firebase
다음은 클라이언트에서 인증에 사용되는 config 파일을 생성한다
// renderer/firebase/firebaseClient.ts
import { getApp, getApps, initializeApp } from "firebase/app"
import { getFirestore } from "firebase/firestore"
import { getAuth } from "firebase/auth"
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_API_KEY,
authDomain: process.env.NEXT_PUBLIC_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_SESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_ID,
measurementId: process.env.NEXT_PUBLIC_MEASUREMENT_ID,
}
// Initialize Firebase
const firebaseClientApp = !getApps.length ? initializeApp(firebaseConfig) : getApp()
const firebaseClientAuth = getAuth(firebaseClientApp)
const db = getFirestore(firebaseClientApp)
export { firebaseClientApp, firebaseClientAuth, db }
firebaseClientApp : initializeApp 함수에 변수로 선언한 firebaseConfig를 인자로 넣은 구현체이다
firebaseClientAuth : 이렇게 구현체를 firebaseClientApp이란 매개변수로 설정하고 getAuth 함수를 통해 인증을 하게된다
db : Firestore DB에 접근할 수 있도록 getFirestore 함수를 통해 연결 한다
이때, 앱을 리로드 하면서 NextJS가 SDK를 다시 초기화 하는 문제를 방지하도록 getApps의 length 확인을 한다.
Firebase Admin SDK를 서버에 추가하기 위한 config도 설정해준다
이유는 추후에 사용될 본인의 채팅리스트를 DB에서 가져오기 위해 uid 를 받아오기 위함이다.
// renderer/firebase/firebaseAdmin.ts
import * as admin from 'firebase-admin'
const firebaseAdminConfig = {
privateKey: process.env.NEXT_PUBLIC_FIREBASE_PRIVATE_KEY,
clientEmail: process.env.NEXT_PUBLIC_FIREBASE_CLIENT_EMAIL,
projectId: process.env.NEXT_PUBLIC_PROJECT_ID,
}
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert(firebaseAdminConfig),
databaseURL: 'https://nextron-desktop-chat.firebaseio.com',
})
}
export { admin }
Firebase Admin SDK 설정은 이 글을 참고하였다
마지막으로 환경변수를 담을 .env 파일도 만들어준다
// renderer/.env
NEXT_PUBLIC_API_KEY=XXXXXX
NEXT_PUBLIC_AUTH_DOMAIN=nextron-desktop-chat.firebaseapp.com
NEXT_PUBLIC_PROJECT_ID=nextron-desktop-chat
NEXT_PUBLIC_STORAGE_BUCKET=nextron-desktop-chat.appspot.com
NEXT_PUBLIC_SESSAGING_SENDER_ID=XXXXXX
NEXT_PUBLIC_ID=XXXXXX
NEXT_PUBLIC_MEASUREMENT_ID=XXXXXX
NEXT_PUBLIC_FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----비공개 키-----END PRIVATE KEY-----\n",
NEXT_PUBLIC_FIREBASE_CLIENT_EMAIL=XXXXXX
renderer 폴더에 .env 파일을 생성하고 .gitignore 에 추가하면 위 코드와 같이 전역변수로 설정한 정보들을
외부에 노출하지 않고 프로젝트 어디에서든 변수로 사용이 가능하다
(이때 .env 파일은 renderer 폴더내에 두어야만 nextron이 읽어올 수 있다)
// .gitignore
# local env files
.env*.local