0

This is the api.js module which creates a test route:

'use strict';

module.exports = function (app) {
  
  console.log("before route creation");
  app.get("/api/test", (req, res) => {
    res.send("it worked");
  });
  console.log("routes created");
};

In the server.js file, I am importing this module as apiRoutes. Then, I am calling it inside an async function.

const databaseConnection = async (apiRoutes, app) => {
  try {
    await mongoose.connect(`mongodb+srv://replitUser:${process.env.DB_PW}@issuetracker.pbbm6.mongodb.net/myFirstDatabase?retryWrites=true&w=majority`);
    console.log("db connection successful");

    //Routing for API 
    console.log("apiRoutes called");
    apiRoutes(app);  

  } catch (err) {
    console.log("an err occurred", err);
  }
}
databaseConnection(apiRoutes, app);
// apiRoutes(app);

The strings "before route creation" and "routes created" are logged to the console. However, the route does not seem to work, although no errors are occurring.

If I call apiRoutes outside of the async function, like here:

const databaseConnection = async (apiRoutes, app) => {
  try {
    await mongoose.connect(`mongodb+srv://replitUser:${process.env.DB_PW}@issuetracker.pbbm6.mongodb.net/myFirstDatabase?retryWrites=true&w=majority`);
    console.log("db connection successful");

    //Routing for API 
    // console.log("apiRoutes called");
    // apiRoutes(app);  

  } catch (err) {
    console.log("an err occurred", err);
  }
}
databaseConnection(apiRoutes, app);
apiRoutes(app);

...it will create the test route successfully.

I've tried to create the route directly inside of the async function and not in a new module, and it changed nothing, the route is still not created.

const databaseConnection = async (apiRoutes, app) => {
  try {
    await mongoose.connect(`mongodb+srv://replitUser:${process.env.DB_PW}@issuetracker.pbbm6.mongodb.net/myFirstDatabase?retryWrites=true&w=majority`);
    console.log("db connection successful");

    //Routing for API 
    // console.log("apiRoutes called");
    // apiRoutes(app);  
    app.get("/api/test", (req, res) => {
      res.send("it worked");
    });
  } catch (err) {
    console.log("an err occurred", err);
  }
}
databaseConnection(apiRoutes, app);

Why can't I create routes inside of an async function?

Here is a link to the project on replit - Feel free to fork

dasJulian
  • 497
  • 6
  • 21
  • 2
    why not simply make server.js which will connect to db and then instantiate app based on express? – num8er Oct 28 '21 at 21:46
  • Warning that this `app.route('/:project/').get(...)` registers a wildcard route that will match ALL top level routes. This is usually a bad practice and can lead to problems as it will even match simple things like `/contacts`, `/help`, etc.. that you don't want taken as project names because your front-end may want to use various top level URLS in its design. It is a lot safer to do `app.get("/project/:project", ...)` as that creates a special URL namespace just for your project URLs so top level URLs are available for your app's uses. – jfriend00 Oct 28 '21 at 21:58

2 Answers2

1

I know it's not direct answer to Your question.

But the problem is that You cannot structure Your code correctly.

So in below You can see app structure with separate db and app module where app starts listening after db connection.

server.js

const http = require('http');

const db = require('./db');
const app = require('./app');

const server = http.createServer(app);
const PORT = process.env.PORT || 8000;

(async () => {
  await db.connect();
  console.log('connected to db');

  server.listen(PORT, () => {
    console.log(`app listening at port: ${PORT}`);
  });      
})();

db.js

const mongoose = require('mongoose');

const UserSchema = require('./schemas/User');
mongoose.model('User', UserSchema);

module.exports = {
  connect: function() {
    const dsn = `mongodb+srv://replitUser:${process.env.DB_PW}@issuetracker.pbbm6.mongodb.net/myFirstDatabase?retryWrites=true&w=majority`;
    return mongoose.connect(dsn);
  },
  model: function(name) {
    return mongoose.model(name);
  },
};

app.js

const express = require('express');
const app = express();

const routes = require('./routes');
app.use(routes);

module.exports = app;

routes/index.js

const express = require('express');
const router = express.Router();

const users = require('./users');
router.use('/api/users', users);

module.exports = router;

routes/users.js

const express = require('express');
const router = express.Router();

const db = require('../db');
const User = db.model('User');

router.get('/', async (req, res) => {
  const users = await User.find({}).lean();
  res.status(200).send({users});
});

router.get('/:id', async (req, res) => {
  const user = await User.findById(req.params.id).lean();
  if (!user) {
    return res.status(404).end();
  }
  res.status(200).send(user);
});

module.exports = router;

schemas/User.js

const mongoose = require('mongoose');
const {Schema} = mongoose;

const UserSchema = new Schema({
  username: Schema.Types.String,
  password: Schema.Types.String,
  name: Schema.Types.String,
});

module.exports = UserSchema;
num8er
  • 18,604
  • 3
  • 43
  • 57
0

At first, I thought that using express.Router() was an easy fix. Here is what this would have looked like:

Create a router and mount it to the /api path:

const router = express.Router();
app.use("/api", router);

Then, create the route with router.get() instead of app.get().

const databaseConnection = async (router) => {
  try {
    await mongoose.connect(`mongodb+srv://replitUser:${process.env.DB_PW}@issuetracker.pbbm6.mongodb.net/myFirstDatabase?retryWrites=true&w=majority`);
    console.log("db connection successful");

    //Routing for API 
    router.get("/test", (req, res) => {
      res.send("it worked");
    });
  } catch (err) {
    console.log("an err occurred", err);
  }
}
databaseConnection(router);

Here is a link to the complete version of this

But as user @num8er mentioned, this structure is bad practice and unnecessary.

I didn't understand that there is no need to create the routes after the database connection. Mongoose lets you use models before the database is connected because mongoose buffers these database calls internally. The server just needs to listen after the database connection.

So now I've implemented @num8er's thoughts and updated the project!

I've got a db.js module, which exports a connect function that returns a promise:

const mongoose = require("mongoose");

module.exports = {
  connect: function() {
    const dsn = `mongodb+srv://replitUser:${process.env.DB_PW}@cluster0.m31tz.mongodb.net/myFirstDatabase?retryWrites=true&w=majority`;
    return mongoose.connect(dsn);
  },
}

In the server.js file, I create the routes just by executing the api.js module:

const apiRoutes = require("./routes/api.js");
const router = express.Router();
app.use("/", router);
apiRoutes(router);

And then I start listening after I'm connected to the database:

(async () => {
  const db = require("./db");
  try {
    await db.connect();
    console.log("connected to db");
  } catch (err) {
    console.log("an error occurred while connecting to db", err);
  }

  //Start our server and tests!
  const listener = app.listen(process.env.PORT || 3000, function () {
    console.log("Your app is listening on port " + listener.address().port);
    if (process.env.NODE_ENV === "test") {
      console.log("Running Tests...");
      setTimeout(function () {
        try {
          runner.run();
        } catch (e) {
          console.log("Tests are not valid:");
          console.error(e);
        }
      }, 5000);
    }
  });
})();

Here is a link to the complete fixed project on replit

Attention: There is a problem with the chai tests on replit, as explained here. However, if you download the files and run the server locally, everything works fine.

dasJulian
  • 497
  • 6
  • 21
  • 1
    You're putting route handler to databaseConnection method. Have You heard about clean code practices and single responsibility principle? In fact databaseConnection is not providing single responsibility - cause it's role is overloaded with taking care of attaching router to handlers. – num8er Oct 30 '21 at 09:24
  • 1
    btw if You cannot structure backend code so get structured frameworks like: adonis (https://adonisjs.com/ ) or sailsjs (https://sailsjs.com/) or nestjs (https://nestjs.com/) – num8er Oct 30 '21 at 09:29
  • I've tried to implement your thoughts, but now [I ran into a different issue.](https://stackoverflow.com/questions/69782225/chai-mocha-tests-stop-express-server-from-listening) I'll update this answer after all issues are resolved. – dasJulian Oct 31 '21 at 16:59
  • updated the answer – dasJulian Nov 14 '21 at 22:21