Extending express (NodeJS)

Extending express

You want to add extra middleware to express or define your own route endpoints? Here we will explain you how you can do this.

Location

All custom code extending express are placed in extensions/server_connect/routes.

File Structure

Here is a sample of how such a file looks like.

exports.handler = function(app) {
  app.get('/hello', (req, res) => {
    res.send('Hello World');
  });
}

The module should export a handler method that accepts the express app as parameter. For the express API check https://expressjs.com/en/4x/api.html.

Login with Facebook Sample

To create a login with facebook we are going to use Passport.js which is a library for express to authenticate users. Documentation can be found at: http://www.passportjs.org/docs/facebook/.

First we will need to install passport and passport-facebook.

npm i passport passport-facebook

Next we are going to write our handler, we will create a file extensions/server_connect/routes/login.js with the following content:

const passport = require('passport');
const FacebookStrategy = require('passport-facebook').Strategy;

passport.use(new FacebookStrategy({
    clientID: FACEBOOK_APP_ID,
    clientSecret: FACEBOOK_APP_SECRET,
    callbackURL: "http://www.example.com/auth/facebook/callback"
  },
  function(accessToken, refreshToken, profile, done) {
    // Here you want to lookup or create the user in the database
    /* User.findOrCreate(..., function(err, user) {
      if (err) { return done(err); }
      done(null, user);
    }); */
  }
));

exports.handler = function(app) {
  // Redirect the user to Facebook for authentication.  When complete,
  // Facebook will redirect the user back to the application at
  //     /auth/facebook/callback
  app.get('/auth/facebook', passport.authenticate('facebook'));

  // Facebook will redirect the user to this URL after approval.  Finish the
  // authentication process by attempting to obtain an access token.  If
  // access was granted, the user will be logged in.  Otherwise,
  // authentication has failed.
  app.get('/auth/facebook/callback',
    passport.authenticate('facebook', {
      successRedirect: '/',
      failureRedirect: '/login'
    })
  );
};

You need to replace FACEBOOK_APP_ID and FACEBOOK_APP_SECRET with the App ID and App Secret from Facebook. In the callback url you need to replace http://www.example.com with your own domain.

After authentication in the callback function you want to search the user in your database or create it when it doesn’t exist. You can also add extra scopes to get access to the users data or post using his account. For this you will need the accessToken that is returned to call the Facebook API with it.

At last you need to add a link or a button on your page for your user to login like:

<a href="/auth/facebook">Login with Facebook</a>
10 Likes

Awesome!!!

top top top :heart_eyes: :star_struck:

@patrick how are custom modules and routes connected? Say I want to built a passport integration with several strategies. A user can select the strategy they want and fill in fields for the options. How would I set FACEBOOK_APP_ID in the custom route to read the options from the custom module?

There is no direct communication possible to custom modules, but both use the same express instance. You can use for example sesions to pass variables.

The custom routes are loaded before the Server Connect routes, so you can add middleware that extends the req object, the req object can also be accessed from custom modules.

Something like the FACEBOOK_APP_ID is probably best placed in the server environment, you can then get it using process.env from any custom route/module/formatter.

1 Like

Indeed a key wasn’t the best of the examples as it should be separated from code.

But I didn’t mean to say that the key should be hard-coded in the module.

Say I want to set it to a specific ENV variable. I can think of some providers that don’t let you chose the env variable where they store some piece of data.

Just seing how core modules can use the options object from the SC step I thought that maybe it was possible to read from a custom route. But of course, custom modules wouldn’t generate a SC core json file.

"steps": [
      "s3/scaleway",
      {
        "name": "sign",
        "module": "s3",
        "action": "signUploadUrl",
        "options": {
          "provider": "scaleway",
          "bucket": "",
          "key": "{{$_ENV.NODE_ENV+'/img/user/'+$_SESSION.security1Id+'/profile/profile.jpeg'}}",
          "acl": "public-read"
        },
        "outputType": "text",
        "output": false
      },

Potentially I could write a json file from the custom module with the configuration and then read it from the custom route, right? Same as with security providers, connection and S3 providers. Please correct me If I am making wrong assumptions.

You could use the dotenv package (https://www.npmjs.com/package/dotenv).

Just create a file in extensions/server_connect/routes and in there place

require('dotenv').config()

reate a .env file in the root directory of your project. Add environment-specific variables on new lines in the form of NAME=VALUE . For example:

DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3

process.env now has the keys and values you defined in your .env file.

You now can access it in your action steps as {{$_ENV.DB_HOST}}.

2 Likes

Thanks for the tips. I plan to use dotenv extensively.

However I probably forgot to mention one thing that gives better context to my question. And that is sharing my custom module and routing with the community so they can copy the files and start using it.

The same reason you guys use json files to store module options for us is the same reason I am asking about this. With the ENV variable approach I would enforce an opinionated way of configuring the module for others and while that is an option of course I would rather build something that doesn’t enforce on others additional packages.

So if I create a json file with the module options and then read that from the route I guess I am covering more ground. Then it would be up to the user to decide if they want to hard code the strings, bring them from the database or use environment variables. Right?

At this moment I don’t know if this wouldn’t be possible at all thus you pushing me to the ENV route or it is possible but I failed to provide that piece of context.

I think we have to allow definition of Server Connect global variables. Then you can bind those to ENV vars or static values and reuse them through all server connect actions

At your own pace and only if it makes sense. This is completely low priority for me. I just wanted to understand when I build modules how to approach options to make it easy for others and not opinionated.

The example of passportjs, the different strategies available that require their own config(via custom module) and the need of using routing middleware to build the handler just clicked inside of me so I just wanted to understand how to glue everything.

All the stuff you guys have introduced in probably the last year are way above my head. But congratulations on building a top notch tool. You guys seem to be killing it! :beers:

You would be surprised how many lines of code it took them.

Spoiler alert

7

Some people may think “what the heck! Less beers and more coding!”

I think “these guys planned all this months ago and they built everything so that they only needed 7 lines of code today.”

Hats off!

3 Likes

4 Likes

Ah, I was just working out how I was going to integrate my Wappler front-end, private customer portal, public Api on express, and Forest Admin for admin back-end.

This’ll do it!

Well done guys.

Hi @george and @patrick

Are you planning on extending Security Provider so we can plug into it?

1 Like

@patrick
I finally had a use case for this and it works realy great.
I integrated DataTables Editor with server side data. Thank you for this.

Although I now realise as Jonas has mentioned, there are no security options available here.
Is there a way to code it right now? Or some other workaround to make it check security restrict status?

@JonL Do you have any insights on this?

I am afraid not sid. Hopefully something can be done.

I think I found a way - sessions.
Added const session = require('express-session'); to the extended route.
And added condition at the top of the route:

if (req.session.is_logged_in != 1)
            res.sendStatus(401);

On login, I created a session “is_logged_in”, which I delete on logout.
There are other security provider related session variables here too. I just found creating separate session variable more fitting for my use case.
This works well in my testing.

1 Like