웹용 Firebase 알아보기

1. 개요

이 Codelab에서는 대화형 웹 애플리케이션을 만들기 위한 Firebase의 기본사항을 알아봅니다. 여러 Firebase 제품을 사용하여 이벤트 RSVP 및 방명록 채팅 앱을 빌드합니다.

이 단계의 스크린샷

학습 내용

  • Firebase 인증 및 FirebaseUI로 사용자 인증
  • Cloud Firestore를 사용하여 데이터를 동기화합니다.
  • Firebase 보안 규칙을 작성하여 데이터베이스를 보호합니다.

필요한 사항

  • 원하는 브라우저(예: Chrome)
  • stackblitz.com 액세스 (계정 또는 로그인 필요 없음)
  • Google 계정(예: Gmail 계정) GitHub 계정에 이미 사용하고 있는 이메일 계정을 사용하는 것이 좋습니다. 따라서 StackBlitz에서 고급 기능을 사용할 수 있습니다.
  • Codelab의 샘플 코드 코드를 가져오는 방법은 다음 단계를 참고하세요.

2. 시작 코드 가져오기

이 Codelab에서는 여러 Firebase 워크플로가 통합된 온라인 편집기인 StackBlitz를 사용하여 앱을 빌드합니다. Stackblitz에는 소프트웨어 설치나 특별한 StackBlitz 계정이 필요하지 않습니다.

StackBlitz를 사용하면 프로젝트를 다른 사용자와 공유할 수 있습니다. StackBlitz 프로젝트 URL이 있는 다른 사용자는 내 코드를 보고 프로젝트를 포크할 수 있지만 StackBlitz 프로젝트를 수정할 수는 없습니다.

  1. 시작 코드의 경우 이 URL로 이동합니다. https://stackblitz.com/edit/firebase-gtk-web-start
  2. StackBlitz 페이지 상단에서 Fork를 클릭합니다.

이 단계의 스크린샷

이제 고유한 URL과 함께 고유한 이름이 지정된 자체 StackBlitz 프로젝트로 시작 코드 사본이 만들어졌습니다. 모든 파일과 변경사항이 이 StackBlitz 프로젝트에 저장됩니다.

3. 일정 정보 수정

이 Codelab의 시작 자료는 일부 스타일시트와 앱용 HTML 컨테이너를 포함하여 웹 앱을 위한 구조를 제공합니다. 이 Codelab의 후반부에서는 이러한 컨테이너를 Firebase에 연결합니다.

먼저 StackBlitz 인터페이스를 좀 더 알아봅시다.

  1. StackBlitz에서 index.html 파일을 엽니다.
  2. event-details-containerdescription-container를 찾은 다음 일부 이벤트 세부정보를 수정해 봅니다.

텍스트를 수정하면 StackBlitz의 자동 페이지 새로고침으로 새 이벤트 세부정보가 표시됩니다. 정말 멋지죠?

<!-- ... -->

<div id="app">
  <img src="..." />

  <section id="event-details-container">
     <h1>Firebase Meetup</h1>

     <p><i class="material-icons">calendar_today</i> October 30</p>
     <p><i class="material-icons">location_city</i> San Francisco</p>

  </section>

  <hr>

  <section id="firebaseui-auth-container"></section>

  <section id="description-container">
     <h2>What we'll be doing</h2>
     <p>Join us for a day full of Firebase Workshops and Pizza!</p>
  </section>
</div>

<!-- ... -->

앱의 미리보기는 다음과 같이 표시됩니다.

앱 미리보기

이 단계의 스크린샷

4. Firebase 프로젝트 만들기 및 설정

이벤트 정보를 표시하는 것은 참석자에게 좋지만 이벤트만 표시하는 것은 누구에게도 유용하지 않습니다. 이 앱에 동적 기능을 추가해 보겠습니다. 이를 위해 Firebase를 앱에 연결해야 합니다. Firebase를 시작하려면 Firebase 프로젝트를 만들고 설정해야 합니다.

Firebase 프로젝트 만들기

  1. Firebase에 로그인합니다.
  2. Firebase Console에서 프로젝트 추가(또는 프로젝트 만들기)를 클릭한 후 Firebase 프로젝트의 이름을 Firebase-Web-Codelab으로 지정합니다.

    이 단계의 스크린샷

  3. 프로젝트 만들기 옵션을 클릭하여 진행합니다. 메시지가 표시되면 Firebase 약관에 동의합니다. 이 앱에는 애널리틱스를 사용하지 않으므로 Google 애널리틱스 화면에서 '사용 설정하지 않음'을 클릭합니다.

Firebase 프로젝트에 관해 자세히 알아보려면 Firebase 프로젝트 이해를 참고하세요.

Console에서 Firebase 제품 사용 설정 및 설정

빌드 중인 앱은 웹 앱에 사용할 수 있는 몇 가지 Firebase 제품을 사용합니다.

  • Firebase 인증Firebase UI: 사용자가 앱에 간편하게 로그인할 수 있습니다.
  • Cloud Firestore: 클라우드에 구조화된 데이터를 저장하고 데이터가 변경되면 인스턴트 알림을 받을 수 있습니다.
  • 데이터베이스를 보호하기 위한 Firebase 보안 규칙

이러한 제품 중에는 특수 구성이 필요하거나 Firebase Console을 사용하여 사용 설정해야 하는 제품이 있습니다.

Firebase 인증에 이메일 로그인 사용 설정

사용자가 웹 앱에 로그인할 수 있도록 이 Codelab에서는 이메일/비밀번호 로그인 방법을 사용합니다.

  1. Firebase Console의 왼쪽 패널에서 빌드 > 인증을 클릭합니다. 그런 다음 시작하기를 클릭합니다. 이제 인증 대시보드가 표시됩니다. 여기에서 가입한 사용자를 확인하고 로그인 제공업체를 구성하며 설정을 관리할 수 있습니다.

    이 단계의 스크린샷

  2. 로그인 방법 탭을 선택합니다. 여기를 클릭하면 바로 탭으로 이동합니다.

    이 단계의 스크린샷

  3. 제공업체 옵션에서 이메일/비밀번호를 클릭하고 스위치를 사용으로 전환한 다음 저장을 클릭합니다.

    이 단계의 스크린샷

Cloud Firestore 설정

웹 앱은 Cloud Firestore를 사용하여 채팅 메시지를 저장하고 새 채팅 메시지를 수신합니다.

Firebase 프로젝트에서 Cloud Firestore를 설정하는 방법은 다음과 같습니다.

  1. Firebase Console의 왼쪽 패널에서 빌드를 펼친 다음 Firestore 데이터베이스를 선택합니다.
  2. 데이터베이스 만들기를 클릭합니다.
  3. 데이터베이스 ID(default)로 설정된 채로 둡니다.
  4. 데이터베이스의 위치를 선택한 다음 다음을 클릭합니다.
    실제 앱의 경우 사용자와 가까운 위치를 선택해야 합니다.
  5. 테스트 모드로 시작을 클릭합니다. 보안 규칙에 관한 면책 조항을 읽습니다.
    이 Codelab의 후반부에서 데이터를 보호하는 보안 규칙을 추가합니다. 데이터베이스에 대한 보안 규칙을 추가하지 않은 채 앱을 공개적으로 배포하거나 노출하지 마세요.
  6. 만들기를 클릭합니다.

5. Firebase 추가 및 구성

Firebase 프로젝트를 만들고 일부 서비스를 사용 설정했으므로 이제 Firebase를 사용할 코드와 사용할 Firebase 프로젝트를 지정해야 합니다.

Firebase 라이브러리 추가

앱에서 Firebase를 사용하려면 Firebase 라이브러리를 앱에 추가해야 합니다. Firebase 문서에 설명된 대로 여러 가지 방법으로 추가할 수 있습니다. 예를 들어 Google CDN에서 라이브러리를 추가하거나 npm을 사용하여 로컬에 설치한 후 Browserify를 사용하는 경우 앱에 패키징할 수 있습니다.

StackBlitz는 자동 번들링을 제공하므로 가져오기 문을 사용하여 Firebase 라이브러리를 추가할 수 있습니다. '트리 쉐이킹'이라는 프로세스를 통해 웹페이지의 전체 크기를 줄이는 모듈식 (v9) 버전의 라이브러리를 사용합니다. 모듈식 SDK에 관한 자세한 내용은 문서를 참고하세요.

이 앱을 빌드하려면 Firebase 인증, FirebaseUI, Cloud Firestore 라이브러리를 사용합니다. 이 Codelab에서는 다음 가져오기 문이 이미 index.js 파일 상단에 포함되어 있으며, 진행하면서 각 Firebase 라이브러리에서 더 많은 메서드를 가져올 예정입니다.

// Import stylesheets
import './style.css';

// Firebase App (the core Firebase SDK) is always required
import { initializeApp } from 'firebase/app';

// Add the Firebase products and methods that you want to use
import {} from 'firebase/auth';
import {} from 'firebase/firestore';

import * as firebaseui from 'firebaseui';

Firebase 프로젝트에 Firebase 웹 앱 추가

  1. Firebase Console로 돌아가서 왼쪽 상단의 프로젝트 개요를 클릭하여 프로젝트의 개요 페이지로 이동합니다.
  2. 프로젝트 개요 페이지 중앙에 있는 웹 아이콘 웹 앱 아이콘을 클릭하여 새 Firebase 웹 앱을 만듭니다.

    이 단계의 스크린샷

  3. 앱을 Web App이라는 별칭으로 등록합니다.
  4. 이 Codelab에서는 이 앱의 Firebase 호스팅도 설정합니다 옆의 체크박스를 선택하지 마세요. 지금은 StackBlitz의 미리보기 창을 사용합니다.
  5. 앱 등록을 클릭합니다.

    이 단계의 스크린샷

  6. Firebase 구성 객체를 클립보드에 복사합니다.

    이 단계의 스크린샷

  7. 콘솔로 이동을 클릭합니다. 앱에 Firebase 구성 객체를 추가합니다.
  8. StackBlitz로 돌아가 index.js 파일로 이동합니다.
  9. Add Firebase project configuration object here 주석 줄을 찾은 다음 주석 바로 아래에 구성 스니펫을 붙여넣습니다.
  10. 고유한 Firebase 프로젝트 구성을 사용하여 Firebase를 설정하는 initializeApp 함수 호출을 추가합니다.
    // ...
    // Add Firebase project configuration object here
    const firebaseConfig = {
      apiKey: "random-unique-string",
      authDomain: "your-projectId.firebaseapp.com",
      databaseURL: "https://your-projectId.firebaseio.com",
      projectId: "your-projectId",
      storageBucket: "your-projectId.firebasestorage.app",
      messagingSenderId: "random-unique-string",
      appId: "random-unique-string",
    };
    
    // Initialize Firebase
    initializeApp(firebaseConfig);
    

6. 사용자 로그인 추가 (회신요청)

이제 앱에 Firebase를 추가했으므로 Firebase 인증을 사용하여 사용자를 등록하는 RSVP 버튼을 설정할 수 있습니다.

이메일 로그인 및 FirebaseUI로 사용자 인증하기

사용자에게 이메일 주소로 로그인하라는 메시지를 표시하는 회신요청 버튼이 있어야 합니다. RSVP 버튼에 FirebaseUI를 연결하면 됩니다.FirebaseUI는 Firebase 인증을 기반으로 사전 빌드된 UI를 제공하는 라이브러리입니다.

FirebaseUI에는 다음 두 가지 작업을 실행하는 구성(문서의 옵션 참고)이 필요합니다.

  • FirebaseUI에 이메일/비밀번호 로그인 방법을 사용하겠다고 알립니다.
  • 로그인 성공 콜백을 처리하고 리디렉션을 방지하기 위해 false를 반환합니다. 단일 페이지 웹 앱을 빌드하고 있으므로 페이지가 새로고침되지 않도록 합니다.

FirebaseUI 인증을 초기화하는 코드 추가

  1. StackBlitz에서 index.js 파일로 이동합니다.
  2. 상단에서 firebase/auth 가져오기 문을 찾은 다음 다음과 같이 getAuthEmailAuthProvider를 추가합니다.
    // ...
    // Add the Firebase products and methods that you want to use
    import { getAuth, EmailAuthProvider } from 'firebase/auth';
    
    import {} from 'firebase/firestore';
    
  3. 다음과 같이 initializeApp 바로 뒤에 인증 객체 참조를 저장합니다.
    initializeApp(firebaseConfig);
    auth = getAuth();
    
  4. FirebaseUI 구성은 시작 코드에 이미 제공되어 있습니다. 이메일 인증 제공업체를 사용하도록 이미 설정되어 있습니다.
  5. index.jsmain() 함수 하단에 다음과 같이 FirebaseUI 초기화 문을 추가합니다.
    async function main() {
      // ...
    
      // Initialize the FirebaseUI widget using Firebase
      const ui = new firebaseui.auth.AuthUI(auth);
    }
    main();
    
    

HTML에 참석 여부 확인 버튼 추가

  1. StackBlitz에서 index.html 파일로 이동합니다.
  2. 아래 예와 같이 event-details-container 내에 RSVP 버튼의 HTML을 추가합니다.

    이 Codelab의 경우 index.js 파일에 이러한 특정 ID의 후크가 이미 있으므로 아래와 같이 동일한 id 값을 사용해야 합니다.

    index.html 파일에는 ID가 firebaseui-auth-container인 컨테이너가 있습니다. FirebaseUI에 전달하여 로그인을 유지하는 ID입니다.
    <!-- ... -->
    
    <section id="event-details-container">
        <!-- ... -->
        <!-- ADD THE RSVP BUTTON HERE -->
        <button id="startRsvp">RSVP</button>
    </section>
    <hr>
    <section id="firebaseui-auth-container"></section>
    <!-- ... -->
    
    앱 미리보기

    이 단계의 스크린샷

  3. RSVP 버튼에 리스너를 설정하고 FirebaseUI 시작 함수를 호출합니다. 이렇게 하면 FirebaseUI에 로그인 창을 확인하려 한다고 알릴 수 있습니다.

    다음 코드를 index.jsmain() 함수 하단에 추가합니다.
    async function main() {
      // ...
    
      // Listen to RSVP button clicks
      startRsvpButton.addEventListener("click",
       () => {
            ui.start("#firebaseui-auth-container", uiConfig);
      });
    }
    main();
    

앱 로그인 테스트

  1. StackBlitz의 미리보기 창에서 회신요청 버튼을 클릭하여 앱에 로그인합니다.
    • 이 Codelab에서는 이메일 인증 단계를 설정하지 않으므로 가짜 이메일 주소를 포함한 모든 이메일 주소를 사용할 수 있습니다.
    • auth/operation-not-allowed 또는 The given sign-in provider is disabled for this Firebase project 오류 메시지가 표시되면 Firebase Console에서 이메일/비밀번호를 로그인 제공업체로 사용 설정했는지 확인합니다.
    앱 미리보기

    이 단계의 스크린샷

  2. Firebase Console의 인증 대시보드로 이동합니다. 사용자 탭에 앱에 로그인하기 위해 입력한 계정 정보가 표시됩니다.

    이 단계의 스크린샷

UI에 인증 상태 추가하기

그런 다음 UI에 로그인되어 있는지 반영되는지 확인합니다.

Firebase 인증 상태 리스너 콜백을 사용합니다. 이 콜백은 사용자의 로그인 상태가 변경될 때마다 알림을 받습니다. 현재 로그인한 사용자가 있다면 앱에서 '회신요청' 버튼을 '로그아웃' 버튼으로 전환합니다.

  1. StackBlitz에서 index.js 파일로 이동합니다.
  2. 상단에서 firebase/auth import 문을 찾은 다음 다음과 같이 signOutonAuthStateChanged를 추가합니다.
    // ...
    // Add the Firebase products and methods that you want to use
    import {
      getAuth,
      EmailAuthProvider,
      signOut,
      onAuthStateChanged
    } from 'firebase/auth';
    
    import {} from 'firebase/firestore';
    
  3. main() 함수 하단에 다음 코드를 추가합니다.
    async function main() {
      // ...
    
      // Listen to the current Auth state
      onAuthStateChanged(auth, user => {
        if (user) {
          startRsvpButton.textContent = 'LOGOUT';
        } else {
          startRsvpButton.textContent = 'RSVP';
        }
      });
    }
    main();
    
  4. 버튼 리스너에서 현재 사용자가 있는지 확인하고 사용자를 로그아웃합니다. 이렇게 하려면 현재 startRsvpButton.addEventListener를 다음으로 바꿉니다.
    // ...
    // Called when the user clicks the RSVP button
    startRsvpButton.addEventListener('click', () => {
      if (auth.currentUser) {
        // User is signed in; allows user to sign out
        signOut(auth);
      } else {
        // No user is signed in; allows user to sign in
        ui.start('#firebaseui-auth-container', uiConfig);
      }
    });
    

이제 앱의 버튼에 로그아웃이 표시되고 버튼을 클릭하면 RSVP로 다시 전환됩니다.

앱 미리보기

이 단계의 스크린샷

7. Cloud Firestore에 메시지 쓰기

사용자가 방문한다는 것을 아는 것은 좋지만 게스트에게 앱에서 할 수 있는 다른 것을 제공해 보겠습니다. 방명록에 메시지를 남길 수 있다면 어떨까요? 반갑습니다. 왜 그런지, 누구를 만나고 싶은지 알려줄 수 있습니다.

사용자가 앱에 작성하는 채팅 메시지를 저장하려면 Cloud Firestore를 사용합니다.

데이터 모델

Cloud Firestore는 NoSQL 데이터베이스이며 데이터베이스에 저장된 데이터는 컬렉션, 문서, 필드, 하위 컬렉션으로 분할됩니다. 채팅의 각 메시지를 guestbook라는 최상위 컬렉션에 문서로 저장합니다.

메시지 문서가 여러 개인 게스트북 컬렉션을 보여주는 Firestore 데이터 모델 그래픽

Firestore에 메시지 추가

이 섹션에서는 사용자가 데이터베이스에 새 메시지를 작성할 수 있는 기능을 추가합니다. 먼저 UI 요소 (메시지 필드 및 전송 버튼)의 HTML을 추가합니다. 그런 다음 이러한 요소를 데이터베이스에 연결하는 코드를 추가합니다.

메시지 필드와 전송 버튼의 UI 요소를 추가하는 방법은 다음과 같습니다.

  1. StackBlitz에서 index.html 파일로 이동합니다.
  2. guestbook-container를 찾은 후 다음 HTML을 추가하여 메시지 입력란과 보내기 버튼이 있는 양식을 만듭니다.
    <!-- ... -->
    
     <section id="guestbook-container">
       <h2>Discussion</h2>
    
       <form id="leave-message">
         <label>Leave a message: </label>
         <input type="text" id="message">
         <button type="submit">
           <i class="material-icons">send</i>
           <span>SEND</span>
         </button>
       </form>
    
     </section>
    
    <!-- ... -->
    

앱 미리보기

이 단계의 스크린샷

사용자가 보내기 버튼을 클릭하면 아래의 코드 스니펫이 트리거됩니다. 메시지 입력 필드의 콘텐츠를 데이터베이스의 guestbook 컬렉션에 추가합니다. 특히 addDoc 메서드는 자동으로 생성된 ID가 있는 새 문서를 guestbook 컬렉션에 추가하여 메시지 콘텐츠를 추가합니다.

  1. StackBlitz에서 index.js 파일로 이동합니다.
  2. 상단에서 firebase/firestore 가져오기 문을 찾은 다음 다음과 같이 getFirestore, addDoc, collection를 추가합니다.
    // ...
    
    // Add the Firebase products and methods that you want to use
    import {
      getAuth,
      EmailAuthProvider,
      signOut,
      onAuthStateChanged
    } from 'firebase/auth';
    
    import {
      getFirestore,
      addDoc,
      collection
    } from 'firebase/firestore';
    
  3. 이제 initializeApp 바로 뒤에 Firestore db 객체 참조를 저장합니다.
    initializeApp(firebaseConfig);
    auth = getAuth();
    db = getFirestore();
    
  4. main() 함수 하단에 다음 코드를 추가합니다.

    auth.currentUser.uid는 Firebase 인증에서 로그인한 모든 사용자에게 제공하는 자동 생성된 고유 ID를 참조합니다.
    async function main() {
      // ...
    
      // Listen to the form submission
      form.addEventListener('submit', async e => {
        // Prevent the default form redirect
        e.preventDefault();
        // Write a new message to the database collection "guestbook"
        addDoc(collection(db, 'guestbook'), {
          text: input.value,
          timestamp: Date.now(),
          name: auth.currentUser.displayName,
          userId: auth.currentUser.uid
        });
        // clear message input field
        input.value = '';
        // Return false to avoid redirect
        return false;
      });
    }
    main();
    

로그인한 사용자에게만 방명록 표시

누구에게나 참석자의 채팅 내용을 공개하지 않는 것이 좋습니다. 채팅을 보호하기 위해 할 수 있는 한 가지 방법은 로그인한 사용자만 방명록을 볼 수 있도록 허용하는 것입니다. 따라서 자체 앱의 경우 Firebase 보안 규칙으로 데이터베이스를 보호해야 합니다. 보안 규칙에 관한 자세한 내용은 Codelab 후반부에 있습니다.

  1. StackBlitz에서 index.js 파일로 이동합니다.
  2. onAuthStateChanged 리스너를 수정하여 방명록을 숨기거나 표시합니다.
    // ...
    
    // Listen to the current Auth state
    onAuthStateChanged(auth, user => {
      if (user) {
        startRsvpButton.textContent = 'LOGOUT';
        // Show guestbook to logged-in users
        guestbookContainer.style.display = 'block';
      } else {
        startRsvpButton.textContent = 'RSVP';
        // Hide guestbook for non-logged-in users
        guestbookContainer.style.display = 'none';
      }
    });
    

메시지 전송 테스트

  1. 앱에 로그인되어 있는지 확인합니다.
  2. '안녕하세요!'와 같은 메시지를 입력한 다음 보내기를 클릭합니다.

그러면 Cloud Firestore 데이터베이스에 메시지가 기록됩니다. 그러나 실제 웹 앱에서는 아직 이 메시지가 표시되지 않습니다. 데이터 검색을 구현해야 하기 때문입니다. 다음에 이 작업을 진행합니다.

하지만 Firebase Console에서는 새로 추가된 메시지를 확인할 수 있습니다.

Firebase Console의 Firestore 데이터베이스 대시보드에 새로 추가된 메시지가 포함된 guestbook 컬렉션이 표시됩니다. 메일을 계속 보내면 방명록 컬렉션에 다음과 같은 많은 문서가 포함됩니다.

Firebase Console

이 단계의 스크린샷

8. 메시지 읽기

메일 동기화하기

게스트가 데이터베이스에 메시지를 쓸 수 있지만 아직 앱에서는 메시지를 볼 수 없다는 점이 좋습니다.

메시지를 표시하려면 데이터가 변경될 때 트리거되는 리스너를 추가한 다음 새 메시지를 표시하는 UI 요소를 만들어야 합니다.

앱에서 새로 추가된 메시지를 수신 대기하는 코드를 추가합니다. 먼저 HTML에 메시지를 표시할 섹션을 추가합니다.

  1. StackBlitz에서 index.html 파일로 이동합니다.
  2. guestbook-container에서 ID가 guestbook인 새 섹션을 추가합니다.
    <!-- ... -->
    
      <section id="guestbook-container">
       <h2>Discussion</h2>
    
       <form><!-- ... --></form>
    
       <section id="guestbook"></section>
    
     </section>
    
    <!-- ... -->
    

다음으로, 데이터 변경사항을 수신 대기하는 리스너를 등록합니다.

  1. StackBlitz에서 index.js 파일로 이동합니다.
  2. 상단에서 firebase/firestore 가져오기 문을 찾은 다음 다음과 같이 query, orderBy, onSnapshot를 추가합니다.
    // ...
    import {
      getFirestore,
      addDoc,
      collection,
      query,
      orderBy,
      onSnapshot
    } from 'firebase/firestore';
    
  3. main() 함수 하단에 다음 코드를 추가하여 데이터베이스의 모든 문서 (방명록 메시지)를 순환합니다. 이 코드에서 어떤 일이 일어나고 있는지 자세히 알아보려면 스니펫 아래의 정보를 읽어보세요.
    async function main() {
      // ...
    
      // Create query for messages
      const q = query(collection(db, 'guestbook'), orderBy('timestamp', 'desc'));
      onSnapshot(q, snaps => {
        // Reset page
        guestbook.innerHTML = '';
        // Loop through documents in database
        snaps.forEach(doc => {
          // Create an HTML entry for each document and add it to the chat
          const entry = document.createElement('p');
          entry.textContent = doc.data().name + ': ' + doc.data().text;
          guestbook.appendChild(entry);
        });
      });
    }
    main();
    

데이터베이스의 메시지를 리슨하기 위해 collection 함수를 사용하여 특정 컬렉션에 대한 쿼리를 만들었습니다. 위의 코드는 Chat 메시지가 저장되는 guestbook 컬렉션의 변경사항을 리슨합니다. 메일은 날짜별로 정렬되며 orderBy('timestamp', 'desc')를 사용하여 최신 메시지가 맨 위에 표시됩니다.

onSnapshot 함수는 사용할 쿼리와 콜백 함수라는 두 가지 매개변수를 사용합니다. 콜백 함수는 쿼리와 일치하는 문서가 변경될 때 트리거됩니다. 이는 메일이 삭제, 수정 또는 추가되는 경우일 수 있습니다. 자세한 내용은 Cloud Firestore 문서를 참조하세요.

메일 동기화 테스트하기

Cloud Firestore는 데이터베이스를 구독하는 클라이언트와 데이터를 자동으로 즉시 동기화합니다.

  • 이전에 데이터베이스에서 만든 메시지가 앱에 표시됩니다. 새 메시지는 언제든지 표시되어야 합니다.
  • 여러 창 또는 탭에서 워크스페이스를 열면 탭 간에 메일이 실시간으로 동기화됩니다.
  • (선택사항) Firebase Console의 데이터베이스 섹션에서 직접 새 메시지를 삭제, 수정 또는 추가해 볼 수 있습니다. 변경사항은 UI에 표시됩니다.

수고하셨습니다. 앱에서 Cloud Firestore 문서를 읽고 있습니다.

앱 미리보기

이 단계의 스크린샷

9. 기본 보안 규칙 설정

처음에 Cloud Firestore를 테스트 모드로 설정했습니다. 즉, 데이터베이스가 읽기 및 쓰기에 열려 있습니다. 그러나 테스트 모드는 개발 초기 단계에만 사용해야 합니다. 권장사항에 따라 앱을 개발할 때 데이터베이스의 보안 규칙을 설정해야 합니다. 보안은 앱의 구조와 동작에 필수적입니다.

보안 규칙을 사용하면 데이터베이스의 문서 및 컬렉션에 대한 액세스를 제어할 수 있습니다. 유연한 규칙 구문을 사용하면 전체 데이터베이스에 대한 모든 쓰기 작업부터 특정 문서에 대한 작업까지 모든 내용과 일치하는 규칙을 만들 수 있습니다.

Firebase Console에서 Cloud Firestore의 보안 규칙을 작성할 수 있습니다.

  1. Firebase Console의 빌드 섹션에서 Firestore 데이터베이스를 클릭한 다음 규칙 탭을 선택합니다. 여기를 클릭하여 규칙 탭으로 바로 이동합니다.
  2. 오늘부터 몇 주 후 공개 액세스 기간이 종료되는 다음과 같은 기본 보안 규칙이 표시됩니다.

이 단계의 스크린샷

컬렉션 확인하기

먼저 앱이 데이터를 쓰는 컬렉션을 식별합니다.

  1. 기존 match /{document=**} 절을 삭제하여 규칙이 다음과 같이 표시되도록 합니다.
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
      }
    }
    
  2. match /databases/{database}/documents에서 보호할 컬렉션을 식별합니다.
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /guestbook/{entry} {
         // You'll add rules here in the next step.
      }
    }
    

보안 규칙 추가하기

각 방명록 문서에서 인증 UID를 필드로 사용했으므로 인증 UID를 가져와 문서에 쓰기를 시도하는 사용자에게 일치하는 인증 UID가 있는지 확인할 수 있습니다.

  1. 아래와 같이 규칙 세트에 읽기 및 쓰기 규칙을 추가합니다.
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /guestbook/{entry} {
          allow read: if request.auth.uid != null;
          allow create:
            if request.auth.uid == request.resource.data.userId;
        }
      }
    }
    
  2. 게시를 클릭하여 새 규칙을 배포합니다. 이제 게스트북의 경우 로그인한 사용자만 메시지(모든 메시지)를 읽을 수 있지만 관리자는 사용자 ID를 사용하여 메시지만 만들 수 있습니다. 또한 메시지를 수정하거나 삭제하는 것도 허용되지 않습니다.

유효성 검사 규칙 추가

  1. 데이터 유효성 검사를 추가하여 필요한 필드가 모두 문서에 있는지 확인합니다.
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /guestbook/{entry} {
          allow read: if request.auth.uid != null;
          allow create:
          if request.auth.uid == request.resource.data.userId
              && "name" in request.resource.data
              && "text" in request.resource.data
              && "timestamp" in request.resource.data;
        }
      }
    }
    
  2. 게시를 클릭하여 새 규칙을 배포합니다.

리스너 재설정

이제 앱에서 인증된 사용자만 로그인할 수 있으므로 인증 리스너 내부로 게스트북 firestore 쿼리를 이동해야 합니다. 그러지 않으면 권한 오류가 발생하고 사용자가 로그아웃할 때 앱 연결이 해제됩니다.

  1. StackBlitz에서 index.js 파일로 이동합니다.
  2. 방명록 컬렉션 onSnapshot 리스너를 subscribeGuestbook이라는 새 함수로 가져옵니다. 또한 onSnapshot 함수의 결과를 guestbookListener 변수에 할당합니다.

    Firestore onSnapshot 리스너는 나중에 스냅샷 리스너를 취소하는 데 사용할 수 있는 구독 취소 함수를 반환합니다.
    // ...
    // Listen to guestbook updates
    function subscribeGuestbook() {
      const q = query(collection(db, 'guestbook'), orderBy('timestamp', 'desc'));
      guestbookListener = onSnapshot(q, snaps => {
        // Reset page
        guestbook.innerHTML = '';
        // Loop through documents in database
        snaps.forEach(doc => {
          // Create an HTML entry for each document and add it to the chat
          const entry = document.createElement('p');
          entry.textContent = doc.data().name + ': ' + doc.data().text;
          guestbook.appendChild(entry);
        });
      });
    }
    
  3. 아래에 unsubscribeGuestbook라는 새 함수를 추가합니다. guestbookListener 변수가 null이 아닌지 확인한 다음 함수를 호출하여 리스너를 취소합니다.
    // ...
    // Unsubscribe from guestbook updates
    function unsubscribeGuestbook() {
      if (guestbookListener != null) {
        guestbookListener();
        guestbookListener = null;
      }
    }
    

마지막으로 새 함수를 onAuthStateChanged 콜백에 추가합니다.

  1. if (user) 하단에 subscribeGuestbook()를 추가합니다.
  2. else 문 하단에 unsubscribeGuestbook()를 추가합니다.
    // ...
    // Listen to the current Auth state
    onAuthStateChanged(auth, user => {
      if (user) {
        startRsvpButton.textContent = 'LOGOUT';
        // Show guestbook to logged-in users
        guestbookContainer.style.display = 'block';
        // Subscribe to the guestbook collection
        subscribeGuestbook();
      } else {
        startRsvpButton.textContent = 'RSVP';
        // Hide guestbook for non-logged-in users
        guestbookContainer.style.display = 'none';
        // Unsubscribe from the guestbook collection
        unsubscribeGuestbook();
      }
    });
    

10. 보너스 단계: 배운 내용을 연습해 보기

참석자의 회신요청 상태 기록하기

현재 앱에서는 사람들이 이벤트에 관심이 있는 경우 채팅을 시작할 수 있도록 합니다. 또한 누가 오는지 알 수 있는 유일한 방법은 채팅에 글을 게시해야 한다는 것입니다. 준비를 마치고 참석자 수를 알려주세요.

이벤트에 참석하려는 사용자를 등록하는 전환 버튼을 추가한 다음 참석자 수를 수집합니다.

  1. StackBlitz에서 index.html 파일로 이동합니다.
  2. guestbook-container에서 다음과 같이 YESNO 버튼 집합을 추가합니다.
    <!-- ... -->
      <section id="guestbook-container">
       <h2>Are you attending?</h2>
         <button id="rsvp-yes">YES</button>
         <button id="rsvp-no">NO</button>
    
       <h2>Discussion</h2>
    
       <!-- ... -->
    
     </section>
    <!-- ... -->
    

앱 미리보기

이 단계의 스크린샷

다음으로 버튼 클릭에 대한 리스너를 등록합니다. 사용자가 를 클릭하면 인증 UID를 사용하여 응답을 데이터베이스에 저장합니다.

  1. StackBlitz에서 index.js 파일로 이동합니다.
  2. 상단에서 firebase/firestore 가져오기 문을 찾은 다음 다음과 같이 doc, setDoc, where를 추가합니다.
    // ...
    // Add the Firebase products and methods that you want to use
    import {
      getFirestore,
      addDoc,
      collection,
      query,
      orderBy,
      onSnapshot,
      doc,
      setDoc,
      where
    } from 'firebase/firestore';
    
  3. main() 함수 하단에 다음 코드를 추가하여 RSVP 상태를 리슨합니다.
    async function main() {
      // ...
    
      // Listen to RSVP responses
      rsvpYes.onclick = async () => {
      };
      rsvpNo.onclick = async () => {
      };
    }
    main();
    
    
  4. 그런 다음 attendees라는 새 컬렉션을 만든 다음 RSVP 버튼 중 하나가 클릭되면 문서 참조를 등록합니다. 클릭한 버튼에 따라 참조를 true 또는 false로 설정합니다.

    먼저 rsvpYes의 경우:
    // ...
    // Listen to RSVP responses
    rsvpYes.onclick = async () => {
      // Get a reference to the user's document in the attendees collection
      const userRef = doc(db, 'attendees', auth.currentUser.uid);
    
      // If they RSVP'd yes, save a document with attendi()ng: true
      try {
        await setDoc(userRef, {
          attending: true
        });
      } catch (e) {
        console.error(e);
      }
    };
    
    그런 다음 rsvpNo의 경우도 동일하지만 값이 false입니다.
    rsvpNo.onclick = async () => {
      // Get a reference to the user's document in the attendees collection
      const userRef = doc(db, 'attendees', auth.currentUser.uid);
    
      // If they RSVP'd yes, save a document with attending: true
      try {
        await setDoc(userRef, {
          attending: false
        });
      } catch (e) {
        console.error(e);
      }
    };
    

보안 규칙 업데이트

이미 몇 가지 규칙이 설정되어 있으므로 버튼으로 추가하는 새 데이터가 거부됩니다.

attendees 컬렉션에 추가를 허용합니다.

attendees 컬렉션에 추가할 수 있도록 규칙을 업데이트해야 합니다.

  1. attendees 컬렉션의 경우 인증 UID를 문서 이름으로 사용했으므로 이를 가져와서 제출자의 uid가 작성 중인 문서와 동일한지 확인할 수 있습니다. 비공개 데이터가 없으므로 모든 사람이 참석자 목록을 읽을 수 있지만 작성자만 업데이트할 수 있습니다.
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        // ... //
        match /attendees/{userId} {
          allow read: if true;
          allow write: if request.auth.uid == userId;
        }
      }
    }
    
  2. 게시를 클릭하여 새 규칙을 배포합니다.

유효성 검사 규칙 추가

  1. 예상되는 모든 필드가 문서에 있는지 확인하는 데이터 검사 규칙을 추가합니다.
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        // ... //
        match /attendees/{userId} {
          allow read: if true;
          allow write: if request.auth.uid == userId
              && "attending" in request.resource.data;
    
        }
      }
    }
    
  2. 게시를 클릭하여 규칙을 배포하세요.

(선택사항) 이제 버튼을 클릭한 결과를 볼 수 있습니다. Firebase Console에서 Cloud Firestore 대시보드로 이동합니다.

회신요청 상태 읽기

이제 응답을 기록했으므로 누가 참석하는지 확인하고 이를 UI에 반영해 보겠습니다.

  1. StackBlitz에서 index.html 파일로 이동합니다.
  2. description-container에서 ID가 number-attending인 새 요소를 추가합니다.
    <!-- ... -->
    
     <section id="description-container">
         <!-- ... -->
         <p id="number-attending"></p>
     </section>
    
    <!-- ... -->
    

다음으로 attendees 컬렉션의 리스너를 등록하고 응답 수를 계산합니다.

  1. StackBlitz에서 index.js 파일로 이동합니다.
  2. main() 함수 하단에 회신요청 상태를 수신 대기하고 YES 클릭수를 집계하는 다음 코드를 추가합니다.
    async function main() {
      // ...
    
      // Listen for attendee list
      const attendingQuery = query(
        collection(db, 'attendees'),
        where('attending', '==', true)
      );
      const unsubscribe = onSnapshot(attendingQuery, snap => {
        const newAttendeeCount = snap.docs.length;
        numberAttending.innerHTML = newAttendeeCount + ' people going';
      });
    }
    main();
    

마지막으로 현재 상태에 해당하는 버튼을 강조 표시해 보겠습니다.

  1. 현재 인증 UID에 attendees 컬렉션에 항목이 있는지 확인하는 함수를 만든 다음 버튼 클래스를 clicked로 설정합니다.
    // ...
    // Listen for attendee list
    function subscribeCurrentRSVP(user) {
      const ref = doc(db, 'attendees', user.uid);
      rsvpListener = onSnapshot(ref, doc => {
        if (doc && doc.data()) {
          const attendingResponse = doc.data().attending;
    
          // Update css classes for buttons
          if (attendingResponse) {
            rsvpYes.className = 'clicked';
            rsvpNo.className = '';
          } else {
            rsvpYes.className = '';
            rsvpNo.className = 'clicked';
          }
        }
      });
    }
    
  2. 또한 구독 취소를 위한 함수를 만들어 보겠습니다. 사용자가 로그아웃할 때 사용됩니다.
    // ...
    function unsubscribeCurrentRSVP() {
      if (rsvpListener != null) {
        rsvpListener();
        rsvpListener = null;
      }
      rsvpYes.className = '';
      rsvpNo.className = '';
    }
    
  3. 인증 리스너에서 함수를 호출합니다.
    // ...
    // Listen to the current Auth state
      // Listen to the current Auth state
      onAuthStateChanged(auth, user => {
        if (user) {
          startRsvpButton.textContent = 'LOGOUT';
          // Show guestbook to logged-in users
          guestbookContainer.style.display = 'block';
    
          // Subscribe to the guestbook collection
          subscribeGuestbook();
          // Subscribe to the user's RSVP
          subscribeCurrentRSVP(user);
        } else {
          startRsvpButton.textContent = 'RSVP';
          // Hide guestbook for non-logged-in users
          guestbookContainer.style.display = 'none'
          ;
          // Unsubscribe from the guestbook collection
          unsubscribeGuestbook();
          // Unsubscribe from the guestbook collection
          unsubscribeCurrentRSVP();
        }
      });
    
  4. 여러 사용자로 로그인해 보세요. 버튼을 클릭할 때마다 횟수가 증가하는 것을 확인하세요.

앱 미리보기

이 단계의 스크린샷

11. 수고하셨습니다.

Firebase를 사용하여 대화형 실시간 웹 애플리케이션을 빌드했습니다.

학습한 내용

  • Firebase 인증
  • FirebaseUI
  • Cloud Firestore
  • Firebase 보안 규칙

다음 단계

  • Firebase 개발자 워크플로에 대해 자세히 알아보고 싶으신가요? 앱을 로컬에서 테스트하고 실행하는 방법을 알아보려면 Firebase 에뮬레이터 Codelab을 확인하세요.
  • 기타 Firebase 제품에 대해 자세히 알아보려면 어떻게 해야 하나요? 사용자가 업로드하는 이미지 파일을 저장하고 싶을 수도 있고, 사용자에게 알림을 보내야 할까요? Firebase 웹 Codelab에서 웹용 Firebase 제품에 대해 자세히 알아볼 수 있는 Codelab을 확인하세요.
  • Cloud Firestore에 대해 자세히 알아보려면 하위 컬렉션과 트랜잭션에 대해 알아보고 싶으신가요? Cloud Firestore에 대해 자세히 알아볼 수 있는 Codelab을 확인하려면 Cloud Firestore 웹 Codelab으로 이동하세요. 또는 Cloud Firestore 알아보기 YouTube 시리즈를 확인하세요.

자세히 알아보기

어땠나요?

여러분의 의견을 기다립니다. 여기에서 간단한 양식을 작성해 주세요.