kakasoo

NestJS에 SpreadSheet API 사용하기 본문

프로그래밍/NestJS

NestJS에 SpreadSheet API 사용하기

카카수(kakasoo) 2023. 1. 1. 13:11
반응형

구글에는 googleapis 라는 라이브러리 (* 아래 링크) 도 있지만 사용하지 않는 것이 좋다.

googleapis

이 라이브러리의 경우 구글에서 제공하는 모든 API가 다 들어 있기 때문에 어플리케이션에서 쓰기엔 버겁다.

용량 자체가 120MB 정도가 되는데 서버 성능이 부족하면, 예를 들어 EC2가 t3.micro, small 급이라면,

그런 경우에는 서버를 빌드할 때 JavaScript의 Heap memory가 부족하다는 오류를 내뱉으며 멈추게 된다.

그렇기 때문에 만약,

  1. 스프레드시트를 생성한다.
  2. 데이터를 넣는다.
  3. 그 시트를 구글 드라이브를 이용해 다른 사람에게 공유한다.

와 같이, 스프레드시트 이후에 연계될 다음 동작들이 있지 않다면 npm에서 적절한 라이브러리를 찾는 게 낫다.

여기서 말하는 적절한 라이브러리는, 필요한 기능만 가지고 있는 더 작은 크기의 라이브러리를 말한다.

npm i google-spreadsheet
npm i --save-dev @types/google-spreadsheet

내가 찾은 라이브러리는 google-spreadsheet 였고, type이 제공되기 때문에 nest에서 용이하다.

google-spreadsheet library

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { readFileSync } from 'fs';
import { join } from 'path';
import { GoogleSpreadsheet, GoogleSpreadsheetRow } from 'google-spreadsheet';

@Injectable()
export class SpreadSheetService {
    private readonly CREDENTIAL_PATH = join(__dirname, 'spread-sheet.json');
    private readonly SCOPES = ['<https://www.googleapis.com/auth/spreadsheets>'];

    constructor() {}

    async loadGoogleSpreadSheet(sheetId: string) {
        try {
            const content = readFileSync(this.CREDENTIAL_PATH, { encoding: 'utf8' });
            const credentials = JSON.parse(content);

            const doc = new GoogleSpreadsheet(sheetId);
            await doc.useServiceAccountAuth({
                client_email: credentials.client_email,
                private_key: credentials.private_key,
            });

            await doc.loadInfo();
            return doc;
        } catch (err) {
            console.log(err.message);
            return false;
        }
    }

    async addRowsToSheet(row: Record<string, string="">, sheetName: string) {
        const sheetId = '1htYnqW1jabJP6azHgRrFyuiE2MleLKx18DPTlyeI3kM';
        const isLoadedDoc = await this.lightLoadGoogleSpreadSheet(sheetId);
        if (isLoadedDoc) {
            await isLoadedDoc.sheetsByTitle[sheetName].addRow(row);
        }
    }
}
</string,>

서비스는 이런 식으로 작성하면 된다.

서비스에 주어진 것은 구글 클라우드에 접근할 수 있도록 하는 API 인증 키로,

클라이언트의 이메일과 private key 를 가지고 있다.

순수하게 구글 라이브러리만 사용한다면 인증과, 인증이 없을 시의 로그인까지 구현해야 한다.

여기서 spread-sheet.json의 형식은 아래와 같다.

google API Key

{
    "type": "service_account",
    "project_id": "project_id",
    "private_key_id": "private_key_id",
    "private_key": "",
    "client_email": "my.iam.gserviceaccount.com",
    "client_id": "",
    "auth_uri": "",
    "token_uri": "",
    "auth_provider_x509_cert_url": "",
    "client_x509_cert_url": ""
}

키는 이런 형태다.

일부러 모든 값을 지웠는데, 이중에서 spread sheet API는 client_email, private_key만을 사용한다.

private_key는 우리가 알다시피 복호화를 위한 키로, pem 파일과 같이 암호화된 line들로 구성되어 있다.

중요한 것은, 이 키가 가리키는 계정이 해당 시트를 적절한 권한으로 공유받은 상태여야 한다는 것이다.

상식적으로 남의 시트를 API를 통해 제어할 수는 없으니깐.

loadGoogleSpreadSheet method

async lightLoadGoogleSpreadSheet(sheetId: string) {
    try {
        const content = readFileSync(this.CREDENTIAL_PATH, { encoding: 'utf8' });
        const credentials = JSON.parse(content);

        const doc = new GoogleSpreadsheet(sheetId);
        await doc.useServiceAccountAuth({
            client_email: credentials.client_email,
            private_key: credentials.private_key,
        });

        await doc.loadInfo();
        return doc;
    } catch (err) {
        console.log(err.message);
        return false;
    }
}

읽은 document를 반환하는 API를 만들었다.

addRow

async addRowToSheet(row: Record<string, string>, sheetName: string) {
    const sheetId = '1htYnqW1jabJP6azHgRrFyuiE2MleLKx18DPTlyeI3kM';
    const isLoadedDoc = await this.lightLoadGoogleSpreadSheet(sheetId);
    if (isLoadedDoc) {
        await isLoadedDoc.sheetsByTitle[sheetName].addRow(row);
    }
}

sheetId는 google spreadsheet 페이지에 들어가면 바로 확인할 수 있다.

[<https://docs.google.com/spreadsheets/d/sheetId/edit#gid=0>](<https://docs.google.com/spreadsheets/d/sheetId/edit#gid=0>) 라는 URL 중간에서 확인 가능하다.

그 sheetId를 통해서 sheet들을 불러오면, 좌측 하단에 각 시트 별로 이름이 있다.

그 시트의 이름 (sheetName) 을 통해 원하는 시트 페이지만을 꺼내고, 이후 제어하는 게 가능하다.

왜 사용하는가?

데이터베이스에서 수집할 데이터와 구글 애널리틱스에서 수집할 데이터의 형태는 분명히 나뉜다.

그리고 이 둘 만으로도 충분하지만, 사용자에게 직접적으로 데이터를 요구할 경우에는 스프레드시트가 유용하다.

스프레드시트는 설문조사 형태로 된 데이터를 받아서 유저의 응답을 기록하기에 유리하고,

또 데이터베이스와 달리 모든 직군이 열람하기 편리한 형태다.

또 다른 직군이 업무에 활용하는 플랫폼들이 대개 스프레드시트를 통해 데이터를 받을 수 있는 경우가 많다.

예컨대 마케팅 쪽에서 활용하는 파이프드라이브가 그렇다.

개발자보다는 개발자가 아닌 사람들을 위해 작성하는 경우가 많으며, 그들이 인사이트를 얻게 도움을 준다.

반응형