hagopj13 / node-express-boilerplate Goto Github PK
View Code? Open in Web Editor NEWA boilerplate for building production-ready RESTful APIs using Node.js, Express, and Mongoose
License: MIT License
A boilerplate for building production-ready RESTful APIs using Node.js, Express, and Mongoose
License: MIT License
HI @hagopj13, first of all thank you for this boilerplate, it's very useful.
I'm not an expert, but I have a question related to error throwing. Is it a good practice to throw specific error like ApiError from the service layer? I always thought that this kind of error belong to the controller, while the service shouldn't know that the caller is talking using HTTP. Am i wrong? Thank you.
Hi,
I need to add sharp, I know, this is not issue. But The solutions I found are to change docker images, I cant do this.
I have a problem with sharp linux libs.
No error on my workspace without docker. But if deployment via docker, I get error.
Error: 'linux-x64' binaries cannot be used on the 'linuxmusl-x64' platform. Please remove the 'node_modules/sharp' directory and run 'npm install' on the 'linuxmusl-x64' platform.
I am tried this solition. But, there are node permission problems. I didnt install sharp on docker. I need to install sharp on docker for pre-built proccess.
There are some docker images there for sharp with alpine. But I dont want to use them https://hub.docker.com/r/gazf/alpine-sharp
Relation Issue :
https://stackoverflow.com/questions/57932529/linux-x64-binaries-cannot-be-used-on-the-linuxmusl-x64-platform-error
Can I use another docker image? Do you think that makes sense?
If you encounter such a problem, what would your solution be?
I think many projects need image manipulation. Do you think it makes sense to add this to this repository?
Best regards.
Shouldn't the /register
function prevent users from signing up with the same email as another user? Currently I can register two users with the same email and different password, and because of the findOne()
in the /login
, only one user will be able to log in after that.
First of all, thanks for this awesome boilerplate!
I have downloaded the repo in order to check it out and I have found that the operations grouped by tags on Swagger UI are not showing:
When you click on both Auth or Users tags, nothing appears.
This is right after cloning the repo and doing an npm install command.
Not sure if this is related with the configuration (as it seems correct) or the swagger packages. I have tried several browsers with same outcome. No console error message or watsoever.
Hi,
I want to load the relationship with the populate function inside the controller. I also use paginate.
const countPromise = this.countDocuments(filter).exec();
const docsPromise = this.find(filter).populate('community').sort(sort).skip(skip).limit(limit).exec();
.populate('community')
To do this, I edited the plugin part. But I don't want this to work on all models. how can I define a relationship through the controller?
Best regards.
@hagopj13 TypeError: next is not a function
at /home/.../src/utils/catchAsync.js:2:54
at processTicksAndRejections (internal/process/task_queues.js:97:5)
rfr allows to make all requires from the root of the project, reducing the cognitive load by avoiding to have in mind where is a file to realize where a required module lives.
From it's README page:
allows you to require modules in your project with
rfr('lib/module1.js')
instead of something like require('../../lib/module1.js')
I offer myself to make such a change should be the idea accepted.
Hi,
I want to use paginate with search and order params query. But this repo not supported this. I was tried multiple sortBy like this.
My url /endpoint?limit=5&page=1&sortBy[]=stars:desc&sortBy[]=price:asc
The positions of the ranking parameters affect the results. whichever applies first.
But I wonder is this best practice?
Just modified this area
schema.statics.paginate = async function(filter, options) {
const sort = [];
if (options.sortBy) {
Object.entries(options.sortBy)
.forEach(([key, value]) => {
const parts = value.split(':');
sort.push([parts[0], parts[1] === 'desc' ? -1 : 1]);
});
}
Whole code models/plugins.js
const paginate = (schema) => {
/**
* @typedef {Object} QueryResult
* @property {Document[]} results - Results found
* @property {number} page - Current page
* @property {number} limit - Maximum number of results per page
* @property {number} totalPages - Total number of pages
* @property {number} totalResults - Total number of documents
*/
/**
* Query for documents with pagination
* @param {Object} [filter] - Mongo filter
* @param {Object} [options] - Query options
* @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc)
* @param {number} [options.limit] - Maximum number of results per page (default = 10)
* @param {number} [options.page] - Current page (default = 1)
* @returns {Promise<QueryResult>}
*/
schema.statics.paginate = async function(filter, options) {
const sort = [];
if (options.sortBy) {
Object.entries(options.sortBy)
.forEach(([key, value]) => {
const parts = value.split(':');
sort.push([parts[0], parts[1] === 'desc' ? -1 : 1]);
});
}
const limit = options.limit && parseInt(options.limit, 10) > 0 ? parseInt(options.limit, 10) : 10;
const page = options.page && parseInt(options.page, 10) > 0 ? parseInt(options.page, 10) : 1;
const skip = (page - 1) * limit;
const countPromise = this.countDocuments(filter).exec();
const docsPromise = this.find(filter).populate('community').sort(sort).skip(skip).limit(limit).exec();
return Promise.all([countPromise, docsPromise]).then((values) => {
const [totalResults, results] = values;
const totalPages = Math.ceil(totalResults / limit);
const result = {
results,
page,
limit,
totalPages,
totalResults
};
return Promise.resolve(result);
});
};
};
I took as an example : https://stackoverflow.com/a/13587343/4329812
Best regards.
Hi
Thanks for the crisp boilerplate code orchestration.
Regarding the Reset Password feature, the API throws unauthorized error. Could you direct me if I'm missing something?
thanks
~ bw
It would be nice if we could create API documentation from certain annotations in the source code
Hi,
I am trying to access APIs using swagger API,
Steps i did:
I have created a user with admin role, i can see data in mongoDb.
after plain installation I tried to get user but it says
error: Error: Please authenticate at /src/middlewares/auth.js:10:19
where is to add the token generated while user login? Thanks.
Allow authentication with Google sign-in. This includes registering the user (on first login) and integrating with the current access token setup.
Hi @hagopj13, Thank you for your effort.
I could easily create admin users sending email, password and role to the registration endpoint because it reads the hole body when it only take email and password.
Thanks for creating this repo. It's helpful to fire a server up without spending too much time. It would be nicer to have the same repo for the typescript(I know it already supports it but could be good not to spend time converting it from javascript).
Anyway, Good Job!
I am trying to test the /users routes with Postman, but it is returning 404.
Is there a list of the available routes?
I am trying this - GET localhost:3000/v1/users/getUsers/:userId
, but it returns 404
I've seen there's a mix in the code where sometimes async/await
is used and sometimes somePromise.then().catch()
. I'd like to propose to homogenize the code by using always async/await
, and I offer myself to make the PR should the proposal be accepted.
Also, if it's accepted, it should be added to the contribution guideline.
Paginate plugin is not using the latest version https://www.npmjs.com/package/mongoose-paginate-v2. Because of that: select and populate are not working.
...
Hi,
I want to wrap image url in schema results. Because I saved my file name as public/x.jpg.
I need to show url like this : http://localhost:3000/public/x.jpg.
My temprorary bad solution
theaterSchema.methods.transform = function () {
const theater = this;
let data = pick(theater.toJSON(), ['id', 'title', 'body', 'image', 'tags']);
if(!data.image){
return data;
}else {
return Object.assign({}, data, {
image: 'http://localhost:3000/' + data.image
})
}
};
But I dont set with pagination for new pagination feature.
I wonder your best practice.
Best regards.
Push notification is a need of every developer. It will be helpful if you can add this feature in your boilerplate. Thanks in advance.
In the mongo models, I enabled the {timestamps: true}
however when logging out the response the updatedAt and createdAt values are present but never come across in the JSON response to the client I'm not sure why this is also true for paginated responses as well.
@hagopj13 It will be good if we can have the logout api also that will complete the auth cycle. Looking forward with this api soon. Thanks for your efforts.
Hi,
Thank you for great boilerplate! How about adding support for caching in this to optimize the boilerplate more?
Nowadays it seems normal to provide caching out of the box.
Something like Redis with:
Thanks.
Hi,
I need to use auth () to activate users who log in with tokens.
In this case, we can take the user as req.user in the controller and perform the operation.
But in any case, I have to check the req.user information.
my scenario is as follows:
Without auth (), a route is broadcasting publicly.
The user can view this route without logging in.
I want to send an additional field if it is logged in and has a relationship with the content it looks at /ep/54e...
I want to do something like "You have purchased this product before, you don't need to buy it again". /ep/54e.../buy
I have to define auth () under any condition to get the req.user information. but I also want my route to be public.
How can I verify if the request is from a verified user?
How should I use the auth () alternative methods?
Thank you.
Instead of using pick method of lodash, you can use the destructuring concept of javascript core.
Keeping the dependencies minimum.
Hi,
Joi is not part of hapi anymore, thus it's better to use "joi" for latest updates.
HI, i was looking at this test:
test('should return 200 and apply the default query options', async () => {
await insertUsers([userOne, userTwo, admin]);
const res = await request(app)
.get('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send()
.expect(httpStatus.OK);
expect(res.body).toEqual({
results: expect.any(Array),
page: 1,
limit: 10,
totalPages: 1,
totalResults: 3,
});
expect(res.body.results).toHaveLength(3);
expect(res.body.results[0]).toEqual({
id: userOne._id.toHexString(),
name: userOne.name,
email: userOne.email,
role: userOne.role,
});
});
More in detail at this part:
expect(res.body.results[0]).toEqual({
id: userOne._id.toHexString(),
name: userOne.name,
email: userOne.email,
role: userOne.role,
});
});
It seems to me that you can't be sure that the first element is userOne, because there is no default sorting provided and mongo don't ensures a default sort. Am I wrong?
Maybe you can do:
expect(res.body.results).toContainEqual({
id: userOne._id.toHexString(),
name: userOne.name,
email: userOne.email,
role: userOne.role,
});
});
Is there some kind of Documentation that reaches beyond the installation process?
Hi, thanx for this repo, I have a question. How about paginate. Do you have a bast practice?
I have to set page_number and page count meta.
{ "total": 50, "per_page": 15, "current_page": 1, "last_page": 4, "first_page_url": "http://laravel.app?page=1", "last_page_url": "http://laravel.app?page=4", "next_page_url": "http://laravel.app?page=2", "prev_page_url": null, "path": "http://laravel.app", "from": 1, "to": 15, "data":[ { // Result Object }, { // Result Object } ] }
Hello,
I was analyzing the code and apparently the following code will never be executed:
middleware/auth.js
if (!hasRequiredRights && req.params.userId !== user.id) {
return reject(new ApiError(httpStatus.FORBIDDEN, 'Forbidden'));
}
It should be
if (!hasRequiredRights || req.params.userId !== user.id) {
return reject(new ApiError(httpStatus.FORBIDDEN, 'Forbidden'));
}
Or am I wrong? Currently with the && operator the condition will always return false
Debugging via debug | Instead of inserting and deleting console.log you can replace it with the debug function and just leave it there. You can then selectively debug portions of your code by setting DEBUG env variable. If DEBUG env variable is not set, nothing is displayed to the console.
Hi,
Excellent boilerplate! How about adding support for sockets in this to make a more complete boilerplate?
Also instructions on deployment using Docker/native would be very helpful.
Thanks.
I tried to post an request to /v1/auth/register but it error as - message: "email" is required. However, I have given an valid email eventhough its getting rejected. why is this happening ? please let know your thoughts on this. Thanks
{
"name": "admin",
"email": "[email protected]",
"password": "password!!"
}
Also, loved the idea for private fields, but it doesn't work with nested fields. I've made a change on the project I've started from this boilerplate, something like
if (path.includes('.')) {
const [a, b] = path.split('.');
delete ret[a][b];
} else {
delete ret[path];
}
it obviously only work for 1 level of nesting, but changing to allow multiple would be fairly easy. Do you think this is something you'd like to see added to the plugin in this repo, or perhaps in a separated package?
Hi,
I want to generate slug on create model entry. There is have some plugins for this.
https://github.com/talha-asad/mongoose-url-slugs
https://github.com/ladjs/mongoose-slug-plugin
I can use but I am searching native and simple method without plugin. Is this possible?
Thank you.
Hello and thanks once again! the req.user object is showing undefined. Whats the most convenient way to get hold of this to use in my .services.js? Do i use the verifyToken function then pull it from there or is it being attached somewhere else?
Hello,
I was unable to attach the visual studio code debugger to the project.
How can I do this?
Best regards,
Very nice project, thank you for your work
Everything is good but, you have to remove the path lines
For example your code:
@swagger
path
/auth/register:
post:
summary: Register as user
tags: [Auth]
requestBody:
required: true
content:
For example new version swag js doc:
@swagger
/auth/register:
post:
summary: Register as user
tags: [Auth]
requestBody:
required: true
content:
Note: Close the issue like solved.
Hi,
I want to improve my queries with parameters. I have to send an array through parameters and query accordingly. I did it myself manually, but I wonder how to do this without breaking the structure.
As an example, what I want to do is filter a content that has an array. I have to shoot content that has the values I have specified in this way. The query I made is below
endpoint?limit=10&page=1&tags[]=foo&tags[]=bar
When I change the codes in the paginate plugin as follows, what I want to do is exactly. How can I make my query in a simple and beautiful way?
schema.statics.paginate = async function(filter, options) {
...
const countPromise = this.countDocuments(filter).exec();
const docsPromise = this.find({tags: { $in : ['foo', 'bar'] }} ).populate(options.relations).sort(sort).skip(skip).limit(limit).exec();
Thanks.
I am new to Express/NodeJS, so please let me know if I am understanding this incorrectly.
This boilerplate uses 3 different types of JWTs namely Access Tokens, Refresh Tokens and Reset Password Token.
All types of tokens are created with the same content and structure.
const generateToken = (userId, expires, secret = config.jwt.secret) => { const payload = { sub: userId, iat: moment().unix(), exp: expires.unix(), }; return jwt.sign(payload, secret); };
Also the auth middleware does not differenciate between the different types:
`const verifyCallback = (req, resolve, reject, requiredRights) => async (
err,
user,
info
) => {
if (err || info || !user) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'));
}
req.user = user;
if (requiredRights.length) {
const userRights = roleRights.get(user.role);
const hasRequiredRights = requiredRights.every((requiredRight) =>
userRights.includes(requiredRight)
);
if (!hasRequiredRights && req.params.userId !== user.id) {
return reject(new ApiError(httpStatus.FORBIDDEN, 'Forbidden'));
}
}
resolve();
};
const auth = (...requiredRights) => async (req, res, next) => {
return new Promise((resolve, reject) => {
passport.authenticate(
'jwt',
{ session: false },
verifyCallback(req, resolve, reject, requiredRights)
)(req, res, next);
})
.then(() => next())
.catch((err) => next(err));
};
module.exports = auth;`
This leads to all kinds of tokens being valid access tokens.
I am not sure if this is even a problem for security or if it is the intended behavior. It still seems odd to me.
Let me know if I am missing anything.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.