-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Gael
committed
Dec 3, 2018
0 parents
commit 70e60ef
Showing
15 changed files
with
4,505 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Clean architecture for NodeJS Backends | ||
|
||
Example project | ||
|
||
## Install | ||
|
||
yarn | ||
|
||
## Run | ||
|
||
yarn start | ||
|
||
## Test | ||
|
||
yarn test --watch |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
{ | ||
"name": "clean-architecture-node", | ||
"version": "1.0.0", | ||
"description": "", | ||
"scripts": { | ||
"start": "nodemon ./src/index.js", | ||
"lint": "eslint src", | ||
"test": "jest" | ||
}, | ||
"author": "Gael du Plessix", | ||
"license": "ISC", | ||
"dependencies": { | ||
"apollo-server-express": "^1.3.6", | ||
"body-parser": "^1.18.3", | ||
"cors": "^2.8.4", | ||
"dataloader": "^1.4.0", | ||
"eslint": "^4.19.1", | ||
"eslint-plugin-jest": "^21.17.0", | ||
"express": "^4.16.3", | ||
"graphql": "^0.13.2", | ||
"graphql-iso-date": "^3.5.0", | ||
"jest": "^23.2.0", | ||
"mongodb": "^3.1.10", | ||
"nodemon": "^1.17.5", | ||
"prettier": "^1.13.5" | ||
}, | ||
"prettier": { | ||
"trailingComma": "es5", | ||
"singleQuote": true | ||
}, | ||
"eslintConfig": { | ||
"extends": [ | ||
"eslint:recommended", | ||
"plugin:jest/recommended" | ||
], | ||
"plugins": [ | ||
"jest" | ||
], | ||
"parserOptions": { | ||
"ecmaVersion": 2018 | ||
}, | ||
"env": { | ||
"node": true, | ||
"jest/globals": true, | ||
"es6": true | ||
}, | ||
"rules": { | ||
"no-console": 0, | ||
"no-unused-vars": [ | ||
"error", | ||
{ | ||
"argsIgnorePattern": "^_", | ||
"varsIgnorePattern": "^_" | ||
} | ||
] | ||
} | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
const ports = require('./services/ports'); | ||
const accounts = require('./services/accounts'); | ||
const companies = require('./services/companies'); | ||
const networks = require('./services/networks'); | ||
|
||
module.exports = function createContext(_req) { | ||
return { | ||
ports: ports(), | ||
accounts: accounts(), | ||
companies: companies(), | ||
networks: networks(), | ||
}; | ||
}; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
const express = require('express'); | ||
const bodyParser = require('body-parser'); | ||
const cors = require('cors'); | ||
|
||
const { connectDb } = require('./infrastructure/db'); | ||
|
||
const user = require('./user'); | ||
const shopping = require('./shopping'); | ||
|
||
(async () => { | ||
const dbClient = await connectDb(); | ||
|
||
const app = express(); | ||
|
||
app.use(cors()); | ||
app.use(bodyParser.json()); | ||
|
||
// setup a middleware to inject services into every request | ||
app.use((req, _res, next) => { | ||
req.services = { | ||
user: user.getServices({ dbClient }), | ||
shopping: shopping.getServices({ dbClient }), | ||
}; | ||
next(); | ||
}); | ||
|
||
// Before defining modules routes, this is where we would define middlewares for: | ||
// - auth | ||
// - error handling | ||
// - logging | ||
// - ... | ||
|
||
// add a dummy middleware that populates fake auth data | ||
app.use((req, _res, next) => { | ||
req.currentUser = { | ||
_id: '42', | ||
type: 'ADMIN', | ||
}; | ||
next(); | ||
}); | ||
|
||
user.setupREST(app); | ||
shopping.setupREST(app); | ||
|
||
const port = process.env.PORT || 5000; | ||
app.listen(port); | ||
|
||
console.log(`Listening on port ${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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
const { MongoClient } = require('mongodb'); | ||
|
||
async function connectDb() { | ||
const client = await MongoClient.connect( | ||
'mongodb://localhost', | ||
{ useNewUrlParser: true } | ||
); | ||
|
||
console.log('Connected to mongo'); | ||
|
||
// return handle to database | ||
return client.db('db'); | ||
} | ||
|
||
module.exports = { | ||
connectDb, | ||
}; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
const ObjectID = require('mongodb').ObjectID; | ||
|
||
/** | ||
* Builds a mongoDB query filtering for a given list of object ids | ||
* If ids is not an array, returns an empty query (i.e: no filter) | ||
* | ||
* @param {?Array<string>} ids | ||
*/ | ||
const idsFilter = ids => { | ||
if (!Array.isArray(ids)) { | ||
return {}; | ||
} | ||
|
||
return { _id: { $in: ids.map(ObjectID) } }; | ||
}; | ||
|
||
module.exports = { | ||
idsFilter, | ||
}; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
const getServices = ({ dbClient }) => { | ||
return {}; | ||
}; | ||
|
||
const setupREST = app => {}; | ||
|
||
module.exports = { | ||
getServices, | ||
setupREST, | ||
}; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
const serviceFactory = require('../service'); | ||
|
||
const getRepositoryMock = () => ({ | ||
getAccounts: jest.fn().mockResolvedValue([]), | ||
}); | ||
|
||
describe('accountService', () => { | ||
describe('getAccounts()', () => { | ||
it('returns accounts list', async () => { | ||
const service = serviceFactory({ | ||
accountRepository: getRepositoryMock(), | ||
}); | ||
|
||
const testUser = { | ||
type: 'ADMIN', | ||
}; | ||
|
||
await expect(service.getAccounts(testUser)).resolves.toEqual([]); | ||
}); | ||
|
||
it('throws if user is not admin', async () => { | ||
const service = serviceFactory({ | ||
accountRepository: getRepositoryMock(), | ||
}); | ||
|
||
const testUser = { | ||
type: 'USER', | ||
}; | ||
|
||
await expect(service.getAccounts(testUser)).rejects.toThrowError( | ||
'Only admins can list accounts' | ||
); | ||
}); | ||
}); | ||
}); |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
const ACCOUNT_TYPES = { | ||
USER: 'USER', | ||
ADMIN: 'ADMIN', | ||
}; | ||
|
||
module.exports = { | ||
ACCOUNT_TYPES, | ||
}; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
const ObjectID = require('mongodb').ObjectID; | ||
|
||
const { idsFilter } = require('../../infrastructure/dbHelpers'); | ||
|
||
module.exports = ({ dbClient }) => { | ||
const getAccount = async accountId => { | ||
const account = await dbClient.collection('accounts').findOne({ | ||
_id: ObjectID(accountId), | ||
}); | ||
|
||
if (!account) { | ||
// NOTE: here, we should probably throw instead so error handler can return a 404 | ||
return null; | ||
} | ||
|
||
return account; | ||
}; | ||
|
||
const getAccounts = ({ accountIds } = {}) => { | ||
return dbClient | ||
.collection('accounts') | ||
.find({ | ||
...idsFilter(accountIds), | ||
}) | ||
.toArray(); | ||
}; | ||
|
||
const getAccountsForCompany = companyId => { | ||
return dbClient | ||
.collection('accounts') | ||
.find({ company: companyId }) | ||
.toArray(); | ||
}; | ||
|
||
return { | ||
getAccount, | ||
getAccounts, | ||
getAccountsForCompany, | ||
}; | ||
}; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
const handleGetAccount = async (req, res) => { | ||
// extract service from context | ||
const { | ||
services: { | ||
user: { | ||
accountService: { getAccount }, | ||
}, | ||
}, | ||
} = req; | ||
|
||
// extract input from query | ||
const { accountId } = req.params; | ||
|
||
// call service | ||
const account = await getAccount(accountId); | ||
|
||
// handle serialization of result from service | ||
res.json(account); | ||
}; | ||
|
||
const handleGetAccounts = async (req, res) => { | ||
// extract service from context | ||
const { | ||
services: { | ||
user: { | ||
accountService: { getAccounts }, | ||
}, | ||
}, | ||
currentUser, | ||
} = req; | ||
|
||
// call service | ||
const accounts = await getAccounts(currentUser); | ||
|
||
// handle serialization of result from service | ||
res.json(accounts); | ||
}; | ||
|
||
const setupREST = app => { | ||
app.get('/accounts/:accountId', handleGetAccount); | ||
app.get('/accounts/', handleGetAccounts); | ||
}; | ||
|
||
module.exports = { | ||
setupREST, | ||
}; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
const { ACCOUNT_TYPES } = require('./model'); | ||
|
||
module.exports = ({ accountRepository }) => { | ||
const getAccount = accountId => { | ||
return accountRepository.getAccount(accountId); | ||
}; | ||
|
||
const getAccounts = async currentUser => { | ||
// only admins can list accounts | ||
if (currentUser.type !== ACCOUNT_TYPES.ADMIN) { | ||
throw new Error('Only admins can list accounts'); | ||
} | ||
return accountRepository.getAccounts(); | ||
}; | ||
|
||
const getAccountsForCompany = companyId => { | ||
return accountRepository.getAccountsForCompany(companyId); | ||
}; | ||
|
||
return { | ||
getAccount, | ||
getAccounts, | ||
getAccountsForCompany, | ||
}; | ||
}; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
const getServices = ({ dbClient }) => { | ||
const accountRepository = require('./account/repository')({ | ||
dbClient, | ||
}); | ||
|
||
return { | ||
accountService: require('./account/service')({ | ||
accountRepository, | ||
}), | ||
}; | ||
}; | ||
|
||
const setupREST = app => { | ||
require('./account/rest').setupREST(app); | ||
}; | ||
|
||
module.exports = { | ||
getServices, | ||
setupREST, | ||
}; |
Oops, something went wrong.