기본 콘텐츠로 건너뛰기

firebase /w functions + graphQL backend 만들기

persistence 영역을 graphQL 로 일반화 하고 apollo engine 같은 cache를 사용하고 싶다.

firebase의 functions를 통해 firebase를 불러오는 건 사실상 이중 작업인 것 같지만 apollo engine이 매우 맘에 들어 끌어들이고 싶다.

먼저 해볼 것은 functions에 graphql을 집어넣고 정적데이터를 읽어오는 것 먼저 로컬에서 구현해본다.

프로젝트 폴더를 생성하고

firebase init functions 부터 하자.
프로젝트를 선택(혹은 생성하고
? What language would you like to use to write Cloud Functions? JavaScript
✔  Wrote functions/package.json
✔  Wrote functions/index.js
그냥 firebase init 하고 functions를 선택하는 것과는 달리 뭔가 기본 scaffold를 생성해줘서 좋다.
{
  "functions": {
    "source": "functions"
  }
}
디폴트로 이렇게 해주자.

index.coffee 로 get 테스트.
exports하는 놈 이름이 functions 이름이 되고 경로도 /[exports한 놈]/ 이 되는 점이 특징이다.
functions = require 'firebase-functions'
admin = require 'firebase-admin'
admin.initializeApp functions.config().firebase
exports.addMessage = functions.https.onRequest (req, res)->
  original = req.query.text?
  admin.firestore()
    .collection 'messages'
    .add { original }
    .then (writeResult)->
      res.json result: "#{req.query.text} #{writeResult.id} added"
왜때문인지 모르겠는데 firebase serve --only functions 했을 떄 잘 작동하지 않는다.
firebase deploy --only functions 해주자.
$ curl https://us-central1-xxxxx.cloudfunctions.net/addMessage\?text\=mama
{"result":"mama u3rEkSMGDunsdqBpbX2g added"}%
잘 되는 것 같다.
db는 firebase가 아니라 firestore(beta)로 했다.
당연한 이야기지만
functions = require 'firebase-functions'
exports.addMessage = functions.https.onRequest (req, res)->
  original = req.query.text or ''
  res.json result: "#{original} entered"
이런 것도 잘된다.
그럼 일단 apollo server를 끼얹어 보자. functions 디렉토리 아래에서 
npm install --save apollo-server-express graphql-tools graphql express body-parser
패키지를 설치하고
express 먼저 테스트 해보자. functions이름은 api. 즉 /api 로 접근하게 하자.
functions = require 'firebase-functions'
express = require 'express'
app = express()
app.get '*', (req, res)->
  res.send "Hello express!"
exports.api = functions.https.onRequest app
간단히 될거라 생각했으나. 안된다.
curl https://us-central1-xxxxx.cloudfunctions.net/api

Cannot GET null
오류가 난다. 나만 그런건 아니었구나.
curl https://us-central1-xxxxx.cloudfunctions.net/api/
이처럼 /를 추가하니까 작동하는데 /api 가 root가 되서 그런 것 같다.

뭐 실제론 별 문제 아니니 https://www.apollographql.com/docs/apollo-server/example.html 보고 구현해보자.
주의할 점은 web frontend인 /graphiql이 바라보는 endpoint는 /graphql 이 아니라 /api/graphql 이 되는 점만 주의하면 된다.
functions = require 'firebase-functions'
express = require 'express'
bodyParser = require 'body-parser'
{ graphqlExpress, graphiqlExpress } = require 'apollo-server-express'
{ makeExecutableSchema } = require 'graphql-tools'
# fixture
books = [
    title: "Harry Potter and the Sorcerer's stone",
    author: 'J.K. Rowling'
  ,
    title: 'Jurassic Park',
    author: 'Michael Crichton'
]
typeDefs = """
  type Query { books: [Book] }
  type Book { title: String, author: String }
"""
resolvers =
  Query:
    books: => books
schema = makeExecutableSchema { typeDefs, resolvers }
app = express()
# /api/graphql
app.use '/graphql',
  bodyParser.json()
  graphqlExpress { schema, context: {} }
# /api/graphiql
app.use '/graphiql',
  graphiqlExpress endpointURL: '/api/graphql'
exports.api = functions.https.onRequest app
deploy 하고 graphiql을 열어보자.
브라우저에서 https://us-central1-xxxxx.cloudfunctions.net/api/graphiql 로 접근하면

짜자자장!

아하하. 멋져멋져. 박수짝짝.
조금 쉬었다가 apollo engine이나 firebase 연결 같은 걸 시도해봐야겠다.

댓글

이 블로그의 인기 게시물

cURL로 cookie를 다루는 법

http://stackoverflow.com/questions/22252226/passport-local-strategy-and-curl 레거시 소스를 보다보면 인증 관련해서 cookie를 사용하는 경우가 있는데 가령 REST 서버인 경우 curl -H "Content-Type: application/json" -X POST -d '{"email": "aaa@bbb.com", "pw": "cccc"}' "http://localhost/login" 이렇게 로그인이 성공이 했더라도 curl -H "Content-Type: application/json" -X GET -d '' "http://localhost/accounts/" 이런 식으로 했을 때 쿠키를 사용한다면 당연히 인증 오류가 날 것이다. curl의 --cookie-jar 와 --cookie 옵션을 사용해서 cookie를 저장하고 꺼내쓰자. 각각 옵션 뒤엔 저장하고 꺼내쓸 파일이름을 임의로 지정하면 된다. 위의 과정을 다시 수정해서 적용하면 curl -H --cookie-jar jarfile "Content-Type: application/json" -X POST -d '{"email": "aaa@bbb.com", "pw": "cccc"}' "http://localhost/login" curl -H --cookie jarfile "Content-Type: application/json" -X GET -d '' "http://localhost/accounts/" 이렇게 사용하면

MQTT Broker Mosquitto 설치 후 설정

우분투 기준 $ sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa $ sudo apt-get update 하고 $ sudo apt-get install mosquitto 으로 설치하면 서비스까지 착실하게 올라간다. 설치는 간단한데 사용자를 만들어야한다. /etc/mosquitto/mosquitto.conf 파일에서 권한 설정을 변경하자. allow_anonymous false 를 추가해서 아무나 못들어오게 하자. $ service mosquitto restart 서비스를 재시작. 이제 사용자를 추가하자. mosquitto_passwd <암호파일 경로명> <사용자명> 하면 쉽게 만들 수 있다. # mosquitto_passwd /etc/mosquitto/passwd admin Password:  Reenter password:  암호 넣어준다. 두번 넣어준다. 이제 MQTT 약을 열심히 팔아서 Broker 사글세방 임대업을 하자.

MQTT 접속해제 - LWT(Last will and testament)

통신에서 중요하지만 구현이 까다로운 문제로 "상대방이 예상치 못한 상황으로 인하여 접속이 끊어졌을때"의 처리가 있다. 이것이 까다로운 이유는 상대방이 의도적으로 접속을 종료한 경우는 접속 종료 직전에 자신의 종료 여부를 알리고 나갈 수 있지만 프로그램 오류/네트웍 연결 강제 종료와 같은 의도치 않은 상황에선 자신의 종료를 알릴 수 있는 방법 자체가 없기 때문이다. 그래서 전통적 방식으로는 자신의 생존 여부를 계속 ping을 통해 서버가 물어보고 timeout 시간안에 pong이 안올 경우 서버에서 접속 종료를 인식하는 번거로운 방식을 취하는데 MQTT의 경우 subscribe 시점에서 자신이 접속 종료가 되었을 때 특정 topic으로 지정한 메시지를 보내도록 미리 설정할 수 있다. 이를 LWT(Last will and testament) 라고 한다. 선언을 먼저하고 브로커가 처리하게 하는 방식인 것이다. Last Will And Testament 라는 말 자체도 흥미롭다. 법률용어인데  http://www.investopedia.com/terms/l/last-will-and-testament.asp 대략 내가 죽으면 뒷산 xx평은 작은 아들에게 물려주고 어쩌고 하는 상속 문서 같은 내용이다. 즉, 내가 죽었을(연결이 끊어졌을) 때에 변호사(MQTT Broker - ex. mosquitto/mosca/rabbitMQ등)로 하여금 나의 유언(메시지)를 상속자(해당 토픽에 가입한 subscriber)에게 전달한다라는 의미가 된다. MQTT Client 가 있다면 한번 실습해보자. 여러가지가 있겠지만 다른 글에서처럼  https://www.npmjs.com/package/mqtt  을 사용하도록 한다. npm install mqtt --save 로 설치해도 되고 내 경우는 자주 사용하는 편이어서 npm install -g mqtt 로 전역설치를 했다. 호스트는 무료 제공하고 있는 test.mosquitto.org 를