//emuls
$ npm run fb:emuls
//TS:watch function
$ npm run build:watch
루트경로의 package.json
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"fb:login": "firebase login",
"fb:logout": "firebase logout",
"fb:init": "firebase init",
"fb:emuls": "firebase emulators:start --import=./exports --export-on-exit",✅
"fb:deploy": "firebase deplay"
},
"fb:emuls"
에 위와 같은 코드를 추가해줍니다.폴더경로는 원하는 경로로 해도 괜찮습니다.
addDelted
함수는 더이상 사용되지 않기에 지워도 괜찮습니다.src> jobs
폴더를 만들어 index.ts
파일로 addDelted함수를 잘라서 붙여 보관을 해도 좋습니다.import * as admin from 'firebase-admin'
const db = admin.firestore()
//job
async function addDeleted() {
const snaps = await db.collection('Todos').get()
for(const snap of snaps.docs){
await snap.ref.update({
deleted: false
})
}
console.log('끝')
}
하나의 job입니다.
루트 경로에 있는 src가 vite프로젝트입니다. 여기에 파일을 업로드 하는 기능을 만들어보겠습니다.
hosting
이라는 이름으로 설정을 해줍니다.//hosting
$ npm run dev
<template>
<input
type="file"
@change="selectFile" />
</template>
<script lang="ts">
import {defineComponent} from 'vue'
export default defineComponent({
methods: {
selectFile(event: Event) {
const files = (event.target as HTMLInputElement).files as FileList
for(let i = 0; i<files.length; i++) {
const file = files[i]
const reader = new FileReader()
reader.readAsDataURL(file)
reader.addEventListener('load', e => {
console.log((e.target as FileReader).result)
})
}
}
}
})
</script>
이렇게 파일을 문자화한 데이터를 서버에 전송하고 DB에 저장하는 과정을 해볼 예정입니다. DB에는 많은 정보가 들어갈 수 있지만 무거운 정보를 넣는 것은 권장하지 않습니다. 따라서 이미지는 파일 형태에 따로 스토로지에 저장을 합니다. 그리고 DB에는 스토로지에 저장된 파일의 주소를 저장합니다.
todo.ts
투두 아이템을 만들거나 추가할 때 이미지를 넣을 수 있는 구조를 만들어보겠습니다.
interface Todo {
id?: string
title: string,
image?: string, //✅
done: boolean
createdAt: string
updateAt: string,
deleted: boolean
}
...
// 투두추가
router.post('/', async (req, res) => {
const { title, imageBase64 } = req.body
const date = new Date().toISOString()
//스토로지에 파일 저장, bucket은 하나의 폴더정도로 이해해도 좋습니다.
const bucket = admin.storage().bucket('images')
const [, body] = imageBase64.split(',')
//실제 파일화를 하려면 buffer라는 객체가 필요합니다.
const buffer = Buffer.from(body, 'base64')
const file = bucket.file('image.png')
await file.save(buffer)
//변수로 만들어야 재활용이 가능합니다.
const todo: Todo = {
//documnet내용작성
title,
image: 'http://localhost:9199/images/image.png', //스토리지 포트번호
done: false,
createdAt: date,
updateAt: date,
//IOS는 국제표준시로 date를 생성합니다.
deleted: false
}
const bucket = admin.storage().bucket('images')
bucket()
은 하나의 폴더개념으로 이해하면 쉽습니다. images라는 이미지파일을 bucket()이라는 폴더에 담습니다. const [, body] = imageBase64.split(',')
const buffer = Buffer.from(body, 'base64')
const file = bucket.file('image.png')
await file.save(buffer)
image: 'http://localhost:9199/images/image.png',
이미지의 이름과 확장자의 경우 base64코드에서 추출해서 채워넣거나 프론트엔드에서 이미지에 대한 정보를 서버에 전송해 넣어줘야합니다.
App.vue
<script lang="ts">
import {defineComponent} from 'vue'
import axios from 'axios'
export default defineComponent({
methods: {
selectFile(event: Event) {
const files = (event.target as HTMLInputElement).files as FileList
for(let i = 0; i<files.length; i++) {
const file = files[i]
const reader = new FileReader()
reader.readAsDataURL(file)
reader.addEventListener('load', async e => {
console.log((e.target as FileReader).result)
const {data} = await axios({
url: 'http://localhost:5001/kdt-test-98de9/us-central1/api/todo',
method: 'POST',
data: {
title: '파일추가!',
imageBase64: (e.target as FileReader).result
}
})
console.log('투두생성 응답:', data)
})
}
}
}
})
</script>
필요한 예외처리
- 용량제한
- 보안
- 파일형식
등등이 존재합니다.
스토로지 파일 저장 코드의 경우 반복해서 사용이 되고 있기 때문에 별도로 관리를 하는 것이 좋습니다. 모듈화를 시킨다면 utils폴더의 index.ts로 만들어줍니다.
import * as admin from 'firebase-admin'
export async function saveFile(base64: string, bucketName = 'images') {
const bucket = admin.storage().bucket(bucketName)
const [, body] = base64.split(',') //4.
const buffer = Buffer.from(body, 'base64')
const file = bucket.file('image.png')
await file.save(buffer)
return `http://localhost:9199/${bucketName}/image.png`
}
todo.ts
import {saveFile} from '../utils' ✅
router.post('/', async (req, res) => {
const {title, imageBase64} = req.body //1. images를 꺼내옵니다.
const date = new Date().toISOString()
const image= await saveFile(imageBase64) ✅
const todo = {
title,
image, ✅
done: false,
createdAt: date,
updatedAt: date,
deleted: false
}
const ref = await db.collection('Todos').add(todo)
//생성된 데이터를 응답
res.status(200).json({
id: ref.id,
...todo
})
})
//투두수정
router.put('/:id', async(req, res) => {
const {title, done, imageBase64✅} = req.body
const {id} = req.params
const snap = await db.collection('Todos').doc(id).get()
if(!snap.exists) {
return res.status(404).json('존재하지 않는 정보입니다.')
}
const image= await saveFile(imageBase64)✅
const { createdAt } = snap.data() as Todo
const updatedAt = new Date().toDateString()
await snap.ref.update({
title,
image,✅
done,
updatedAt
})
return res.status(200).json({
id: snap.id,
title,
image,✅
done,
createdAt,
updatedAt
})
})
이전에 작성한 데이터들에도 image가 들어갈 수 있도록 해줘야합니다.
utils>index.ts
import * as admin from 'firebase-admin'
const db = admin.firestore()
async function addFields() {
const snaps = await db.collection('Todos').get()
for(const snap of snaps.docs){
const {image} = snap.data() //이미지가 없는 경우에만
if(!image) {
await snap.ref.update({
image: null
})
}
}
console.log('끝')
}
addFields() //✅한번 실행
todo.ts
interface Todo {
id?: string
title: string,
image?: string | null, ✅
done: boolean,
createdAt: string,
updateAt: string,
deleted: boolean
}
src>index.ts
admin.initializeApp()
import * as functions from 'firebase-functions'
import * as express from 'express'
import * as cors from 'cors'
import todo from './routes/todo'
import './jobs' //✅한번 저장 후 바로 주석처리를 해줍니다.
현재 우리가 통신하는 서버는 모두 로컬호스트에 맞춰져있습니다. 이제는 실제 파이어베이스 서버에 배포를 할 것이기에 환경변수를 도입해보도록 하겠습니다.
//hosting
$ npm i -D dotenv
프로젝트의 루트 경로에 .env
파일을 만들어줍니다. 이렇게 만든 .env파일은 프로젝트에서 동작하는 환경에서만 동작합니다. 또한 git에 올라가지 않도록 .gitignore
에 명시를 해줍니다.
NODE_ENV=development
서버에서의 환경변수 지정
- 대표적으로 넷니파이에서는
Build & deploy
에서 지정이 가능합니다.
functions >src > utils> index.ts
import * as admin from 'firebase-admin'
export async function saveFile(base64: string, bucketName = 'images') {
const bucket = admin.storage().bucket(bucketName)
const [, body] = base64.split(',') //4.
const buffer = Buffer.from(body, 'base64')
const file = bucket.file('image.png')
await file.save(buffer)
return process.env.NODE === 'development'
? `http://localhost:9199/${bucketName}/image.png` //로컬주소
: `https://strage.googleapis.com/${bucketName}/image.png` //서버주소
}
루트경로의 index.ts에 사용한다고 설정을 해줍니다.
import * as admin from 'firebase-admin'
admin.initializeApp()
import * as dotenv from 'dotenv'✅
dotenv.config() ✅
환경변수는 서버리스 함수에서 유용하게 활용을 할 수 있습니다