Code Monkey home page Code Monkey logo

mean-auth-app-universal's Introduction

MEAN Auth App with Angular Universal

Last Update: 3/7/2018

This project has been updated with the most recent MEAN versions listed below and has been integrated with Angular Universal for SEO and social media compatibility using server-side rendering. The code of this project is a result of my code-along at the end of part 9 of the video series MEAN Stack Front to Back by Brad Traversy. His original code repo may be found here. Instructions on how to deploy this code to Heroku are also included.


Versions Used

This project was generated with Angular CLI v1.7.3

  • MongoDB v3.6.2 (Mongoose 5.0.9)
  • Express v4.16.2
  • Angular v5.2.8
  • Node.js v9.8.0

Starting the Project

To begin working with this project, perform the following tasks:

  1. Clone this repository to your local machine
  2. Start up your local instance of MongoDB
  3. Run npm install to download dependencies
  4. Run npm start to generate the /dist folder for your Angular front-end, the /dist-server folder for Angular Universal, and to start your Node.js server on http://localhost:3000/.

Development server

Run ng serve for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files. Please note that this will not start your MongoDB connection.

Code Updates to Original Project

The original project by Brad Traversy was made in mid Febuary 2017 with Angular 2. Since that time, there have been numerous code-breaking changes, especially with the release of Angular 5 in early November 2017. Hopefully, this repository will help others code-along to this outstanding video series more easily.

Back-End Updates [Parts 1 - 4]

The following changes should be made to your code when developing your back-end. Most of these updates reflect changes to third-party packages such as Passport.js, bcrpyt, and mongoose.

  • Mongoose has been updated to version 5 and the connection is configured as a Promise.

    mongoose.connect(config.database)
      .then(() => console.log('MongoDB Connected'))
      .catch(err => console.log(err));
  • Use {data: user} in the jwt.sign() method in users.js

    User.comparePassword(password, user.password, (err, isMatch) => {
        if(err) throw err;
        if(isMatch) {
            const token = jwt.sign({data: user}, config.secret, {
  • Use Bearer ${token} (make sure to include the space) in the User.comparePassword() method in users.js

    res.json({ 
        success: true, 
        token: `Bearer ${token}`,
        user: {
            id: user._id,
            name: user.name,
            username: user.username,
            email: user.email
        }
  • Use ExtractJwt.fromAuthHeaderAsBearerToken() method in passport.js

    module.exports = (passport) => {
        let opts = {};
        opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
  • Use jwt_payload.data._id as the argument to the User.getUserById method in passport.js

    passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
            User.getUserById(jwt_payload.data._id, (err, user) => {
  • Nested callback functions have been converted to Promises (optional - example shown below)

    module.exports.addUser = function(newUser, callback){
        bcrypt.genSalt(10, (err, salt) => {
            bcrypt.hash(newUser.password, salt, (err, hash) => {
                if(err) throw err;
                newUser.password = hash;
                newUser.save(callback);
            });
        });
    }

    becomes

    module.exports.addUser = (newUser, callback) => { 
        bcrypt.genSalt(10)
            .then((salt) => bcrypt.hash(newUser.password, salt))
            .then((hash) => {
                newUser.password = hash;
                newUser.save(callback);
            }).catch((err) => console.log('There was an error adding a user.'));
    }

Front-End Updates [Parts 5 - 9]

The code in this section mainly focuses on building the front-end with Angular. This project has been integrated with Angular Universal for server side rendering and because of this, there have been several changes to the original code for compatibility reasons in addition to various UI changes. It's also important to note that the code in this repo is structured differently than in the video series, but the updated changes can be used in your own code-along projects.

Forms

  • Import FormsModule into app.module.ts

    import { FormsModule } from '@angular/common/http';
    ...
    ...
    imports: [
        BrowserModule.withServerTransition({ appId: 'serverApp' }), // BrowserModule if not using Angular Universal
        FormsModule,
        HttpClientModule,
        RouterModule.forRoot(appRoutes),
    ],
  • Template-Driven Forms are used to implement forms in the register.component and login.component files (example below).

    <form #loginForm="ngForm" (ngSubmit)="onLoginSubmit(loginForm)">
        <div class="form-group">
            <label for="username">Username <sup class="red">*</sup></label>
            <input required name="username" id="username" #username="ngModel" ngModel type="email" class="form-control">
        </div>
    ...
    
  • Validation used with *ngIf directive instead of angular2-flash-messages since this package was not compatible with Angular Universal. Code adapted to continue to validate for all fields required and an appropriate e-mail input just as in the original video. The only difference is that the flash messages do not disappear, but the functionality of each webpage was adapted to accomodate for this (example below).

    <h2 class="page-header">Register</h2>
    <div class="alert alert-danger" *ngIf="errSwitch" role="alert">{{ errMsg }}</div>
    <div class="alert alert-success" *ngIf="succSwitch" role="alert">{{ succMsg }}</div>
    

HTTP Client

HTTPClient is used to connect our HTTP API endpoints as the old Http API used in the video series has been deprecated in Angular 5. The following syntax changes should be made in order to properly connect your back-end APIs.

  • Import HTTPClientModule into app.module.ts

    import { HttpClientModule } from '@angular/common/http';
    ...
    ...
    imports: [
        BrowserModule.withServerTransition({ appId: 'serverApp' }), // BrowserModule if not using Angular Universal
        FormsModule,
        HttpClientModule,
        RouterModule.forRoot(appRoutes),
    ],
  • Import HTTPClient into files when using the client

    import { HttpClient } from '@angular/common/http';
    
  • JSON is an assumed default and no longer needs to be explicitly parsed (example from registerUser(user) function in auth.service.ts).

    return this.http.post('http://localhost:3000/users/register', user, { headers })
        .map(res => res.json());

    becomes

    return this.http.post('http://localhost:3000/users/register', user, { headers });
  • Subscribe to observables as usual, but to avoid a pre-compilation error for data.success, set the data argument to type any (example from login.component.ts).

    this.authService.authenticateUser(user).subscribe((data: any) => {
        if (data.success) {
            this.authService.storeUserData(data.token, data.user);
            this.router.navigateByUrl('/dashboard');
  • HTTPHeaders has replaced the deprecated Headers API. HTTPHeaders are immutable so after importing them from @angular/common/http, they must be explicitly appended to the defined header itself. HTTPHeaders are used in auth.service.ts.

    getProfile() {
        this.loadToken();
        let headers = new HttpHeaders();
        headers.append('Content-Type', 'application/json');
        headers.append('Authorization', this.authToken);
        return this.http.get('http://localhost:3000/users/profile', { headers });
    }

    becomes

    getProfile() {
        this.loadToken();
        let headers = new HttpHeaders();
        headers = headers.append('Content-Type', 'application/json');
        headers = headers.append('Authorization', this.authToken);
        return this.http.get('http://localhost:3000/users/profile', { headers });
    }

Other

  • The tokenNotExpired() function was not implemented since the Angular2-jwt package was not compatibile with Angular Universal. Instead, a simple workaround was implemented, which unfortunately does not save the user's application state after exiting the browser window.

    loggedIn() {
        if (this.authToken) {
        return true;
        } else {
        return false;
        }
    }

    If you are not using Angular Universal, id_token should be set as an argument for tokenNotExpired()

    loggedIn() {
        tokenNotExpired(id_token);
    }
  • NotFoundComponent added to app.module.ts to catch all Error: 404 - Page Not Found routes in Angular. There is a separate error handler for 404s with API requests in server.js

    const appRoutes: Routes = [
    { path: '', component: HomeComponent },
        ...
        ...
    { path: '**', component: NotFoundComponent }
    ];
  • Class for fade-in animation added for each page transition in styles.css (optional)

    .fadein {
        -webkit-animation: fadein 1s; /* Safari, Chrome and Opera > 12.1 */
            -moz-animation: fadein 1s; /* Firefox < 16 */
            -ms-animation: fadein 1s; /* Internet Explorer */
                -o-animation: fadein 1s; /* Opera < 12.1 */
                animation: fadein 1s;
    }
    
    @keyframes fadein {
        from { opacity: 0; }
        to   { opacity: 1; }
    }
    ...

Deploying to Heroku

This code may be deployed to Heroku and connected to mLab making it accessible on the internet. This guide has been slightly modified from Part 10 of the video series as this code has been structured differently.

  1. Set up mLab by modifying config/database.js as directed in Part 10
  2. Modify registerUser(user), authenticateUser(user), and getProfile() functions in auth.service.ts to connect to mLab (example below)
    registerUser(user) {
        user.username = user.username.toLowerCase();
        let headers = new HttpHeaders();
        headers = headers.append('Content-Type', 'application/json');
        return this.http.post('http://localhost:3000/users/register', user, { headers });
    }
    becomes
    registerUser(user) {
        user.username = user.username.toLowerCase();
        let headers = new HttpHeaders();
        headers = headers.append('Content-Type', 'application/json');
        return this.http.post('users/register', user, { headers });
    }
  3. Modify the start and postinstall scripts in package.json to:
    "scripts": {
        "ng": "ng",
        "start": "node server.js",
        "build": "ng build",
        "test": "ng test",
        "lint": "ng lint",
        "e2e": "ng e2e",
        "postinstall": "ng build --prod && ng build --prod --app universal --output-hashing=none"
    },
  4. Move the following dependecies from devDependencies to dependencies in package.json:
    "dependencies": {
        ...
        "@angular/cli": "1.6.2",
        "@angular/compiler-cli": "^5.0.0",
        ...
        "typescript": "~2.4.2",
        ...
    },
  5. Deploy to Heroku as directed in Part 10.

Further help

To get more help on the Angular CLI use ng help or go check out the Angular CLI README.

mean-auth-app-universal's People

Contributors

stanleyeosakul avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

mean-auth-app-universal's Issues

Can you please upgrade to latest version

Hi,

Since there are a lot of changes in angular 6 and universal. I could not run angular universal on pure expressjs file.

Can you please help me out. or at least update your repository to latest versions.

Require login after every change to angular?

Hi There,

First of All, THANK YOU for providing this repo, it is really great.

This is a question. I start the express server side using npm start which is all good.

Then in a seperate term I do an ng serve and visit localhost:4200 so I can track changes as I make to angular app,

But.... After every reload (when I change the angular app) I am required to sign in again. The angular app loses its login I guess.

Is this normal behavior? Is this how you use it? I find it slow having to login again after every minor change.

Any advise would be great

Thanks in advance

Nigel

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.