-
-
Save thebrubaker/ca27db43243e068b39f6602ed055effe to your computer and use it in GitHub Desktop.
An example Node gRPC service.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ADDRESS_PORT='localhost:50051' | |
PROTO_PATH='./path/to/file.proto' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const grpc = require('grpc'); | |
const someService = require('./some-external-service'); | |
module.exports = { | |
yourAction: (call, callback) => { | |
const { request } = call; | |
if (!request.someAttribute) { | |
return callback({ | |
code: grpc.status.INVALID_ARGUMENT, | |
message: 'Missing `someAttribute` argument.', | |
}); | |
} | |
const response = { | |
someAttribute: 'foo', | |
} | |
someService.fetchDataOrSomething() | |
.then(() => { | |
callback(null, response); | |
}) | |
.catch(error => { | |
console.error(error); | |
callback({ | |
code: grpc.status.INTERNAL, | |
message: error.message, | |
}); | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const actions = require('./actions'); | |
const someService = require('./some-external-service'); | |
describe('yourAction', () => { | |
beforeAll(() => { | |
someService.fetchDataOrSomething = jest.fn(() => Promise.resolve()); // mock out unrelated functions | |
console.error = jest.fn(); | |
}); | |
afterAll(() => { | |
someService.fetchDataOrSomething.mockRestore(); // restore the function after tests are run | |
console.error.mockRestore(); | |
}); | |
it('describe your action here', done => { | |
const call = { | |
request: { | |
someAttribute: 'fooBar', | |
}, | |
}; | |
actions.yourAction(call, (error, response) => { | |
expect(error).toBeNull(); | |
expect(someService.run).toBeCalled(); // assert external functions were called | |
expect(response).toEqual({ | |
someAttribute: 'foo', | |
}); | |
done(); | |
}); | |
}); | |
it('validates type argument as required', done => { | |
const call = { | |
request: { | |
someAttribute: null, | |
}, | |
}; | |
const callback = error => { | |
expect(error).toEqual(argumentErrorShape); | |
done(); | |
}; | |
actions.yourAction(call, callback); | |
}); | |
it('handles when an external function throws errors', done => { | |
someService.run = jest.fn(() => | |
Promise.reject(Error('Testing error')), | |
); | |
console.error = jest.fn(); | |
const call = { | |
request: { | |
someAttribute: 'fooBar', | |
}, | |
}; | |
const callback = (error, _) => { | |
expect(error).toEqual(someServiceErrorShape); | |
expect(console.error).toBeCalled(); | |
done(); | |
}; | |
create(call, callback); | |
}); | |
}); | |
const argumentErrorShape = expect.objectContaining({ | |
code: expect.any(Number), | |
message: expect.any(String), | |
}); | |
const someServiceErrorShape = expect.objectContaining({ | |
code: 13, | |
message: expect.any(String), | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FROM node:latest | |
WORKDIR /app | |
RUN mkdir /app/storage | |
COPY ./ /app | |
RUN npm install | |
CMD ["npm", "start"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require('dotenv').config(); | |
const path = require('path'); | |
const Server = require('./server'); | |
const { | |
ADDRESS_PORT = '0.0.0.0:50051', | |
PROTO_PATH = '../path/to/file.proto', | |
} = process.env; | |
const server = new Server({ | |
addressPort: ADDRESS_PORT, | |
protoPath: path.join(__dirname, PROTO_PATH), | |
}); | |
server.start(); | |
console.log(`Your gRPC service running on ${ADDRESS_PORT}`); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module.exports = { | |
testEnvironment: 'node', | |
coverageDirectory: './storage/coverage', | |
coverageReporters: ['json-summary', 'text'], | |
collectCoverageFrom: [ | |
'**/*.{js,jsx}', | |
'!**/node_modules/**', | |
'!jest.config.js', | |
'!index.js', | |
], | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "some-service", | |
"version": "1.0.0", | |
"description": "", | |
"main": "index.js", | |
"scripts": { | |
"test": "jest --coverage", | |
"test:unit": "jest unit --coverage", | |
"test:integration": "jest int --coverage", | |
"start": "node index.js", | |
"watch": "nodemon --inspect=0.0.0.0:9222 --nolazy" | |
}, | |
"author": "", | |
"license": "ISC", | |
"devDependencies": { | |
"jest": "^23.6.0", | |
"nodemon": "^1.18.9" | |
}, | |
"dependencies": { | |
"dotenv": "^6.2.0" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const path = require('path'); | |
const Server = require('./server'); | |
const server = new Server({ | |
addressPort: '127.0.0.1:50060', | |
protoPath: path.join(__dirname, '../path/to/file.proto'), | |
}); | |
const client = server.client(); | |
describe('ProtoService', () => { | |
beforeAll(() => { | |
server.start(); | |
}); | |
afterAll(() => { | |
server.stop(); | |
}); | |
it('name your grpc action here', done => { | |
const payload = { | |
someAttribute: 'foo', | |
}; | |
client.yourAction(payload, (error, response) => { | |
expect(error).toBeNull(); | |
expect(response).toEqual(responseShape); | |
done(); | |
}); | |
}); | |
}); | |
const responseShape = expect.objectContaining({ | |
someAttribute: expect.any(String), | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const grpc = require('grpc'); | |
const protoLoader = require('@grpc/proto-loader'); | |
const actions = require('./actions'); | |
module.exports = class Server { | |
constructor({ addressPort, protoPath, credentials }) { | |
this.addressPort = addressPort; | |
this.definition = this.loadPackageDefinition(protoPath); | |
this.credentials = credentials; | |
this.server = new grpc.Server(); | |
} | |
start() { | |
const { ProtoService } = this.definition; // replace with your service name | |
this.server.addService(ProtoService.service, actions); | |
this.server.bind(this.addressPort, this.serverCredentials()); | |
return this.server.start(); | |
} | |
stop() { | |
return this.server.forceShutdown(); | |
} | |
client() { | |
const { ProtoService } = this.definition; // replace with your service name | |
return new ProtoService(this.addressPort, this.clientCredentials()); | |
} | |
loadPackageDefinition(protoPath) { | |
return grpc.loadPackageDefinition( | |
protoLoader.loadSync(protoPath, { | |
keepCase: true, | |
longs: String, | |
enums: String, | |
defaults: true, | |
oneofs: true, | |
}), | |
); | |
} | |
serverCredentials() { | |
if (!this.credentials) { | |
return grpc.ServerCredentials.createInsecure(); | |
} | |
return grpc.ServerCredentials.createSsl( | |
this.credentials.rootCertificate, | |
this.credentials.keyCertificatePairs, | |
this.credentials.checkClientCertificate, | |
); | |
} | |
clientCredentials() { | |
if (!this.credentials) { | |
return grpc.credentials.createInsecure(); | |
} | |
return grpc.credentials.createSsl( | |
this.credentials.rootCertificate, | |
this.credentials.keyCertificatePairs, | |
this.credentials.checkClientCertificate, | |
); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
syntax = "proto3"; | |
service ProtoService { | |
rpc yourAction (Request) returns (Response) {} | |
} | |
message Request { | |
string someAttribute = 1; | |
} | |
message Response { | |
string someAttribute = 1; | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module.exports = { | |
async fetchDataOrSomething() { | |
return await true; | |
}, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment