How would you like to persist user credentials in a MongoDB instance and retrieve them via a Spring Boot application? If so, read on.

MongoDB delivers some great advantages over traditional, relational databases because it's document-focused instead of table-focused. As a result, you don't have to invest a bunch of time in developing a robust schema.

In other words, MongoDB facilitates rapid application development.

That's important in this day and age when everybody needs software yesterday.

In this guide, I'll go over how you can persist user credentials in a MongoDB collection via Spring Boot.

As always, you can follow along or just grab the code on GitHub. Even if you just get the code, though, you'll still need a MongoDB instance somewhere.

Also, if you want to check out the Angular code that only makes a cameo appearance in this guide, you can find it here.

Please note: this guide is a part of an ongoing series of tutorials on how to create a customer relationship management (CRM) application from scratch. The GitHub code for each guide belongs to a specific branch. The master branch holds the entire application up to the current date. If you want to follow the whole series, just view the careydevelopmentcrm tag. Note that the website displays the guides in reverse chronological order so if you want to start from the beginning, go to the last page.

The Business Requirements

Your boss Smithers walks into your office and tells you it's time to start adding some persistence to that CRM app you're working on.

Specifically, he'd like you to persist user data. This seems like a nice time for that since you just implemented a login solution.

"But," Smithers says with his finger in the air. "There's a caveat!"

He tells you that the users who login to the CRM app might also login to other applications in the same "equal system." 

You know he means ecosystem.

In other words, the data store you use to persist user info will be used by other apps as well.

That, by the way, is why you've been working on a Spring Boot application called ecosystem-user-service. It's a microservice that will make itself available to a variety of applications within the same ecosystem.

Further, Smithers says he wants you to persist data in MongoDB because "that's what all the kids are doing these days."

He leaves your office singing a hip-hop song.

Setting up the Database

Before you can do any coding in Spring Boot, you need to create a database. This is where you'll access your Mongo shell.

Once you're in the shell, enter the following command:

use ecosystem

That switches you to a database named ecosystem

Now that you're in that database, enter the following command (but please enter something other than "password" for your password):

db.createUser({ user: "scott", pwd: "password", roles: ["readWrite"] });

That command creates a user that can read and write to the database.

Onward to coding.

Configuring Spring Boot for Persistence With MongoDB

As is usually the case in this series, you're picking up where you left off in the last guide. So get that Spring Boot project front and center in your IDE.

Start by updating the pom.xml file. Add this dependency:

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-mongodb</artifactId>
        </dependency>

Next, create an application.properties file under src/main/resources. Add these lines:

mongo.connection.string=mongodb://scott:password!@server:port
mongo.db.name=ecosystem

mongo.user.collection=users

For the mongo.connection.string property, you'll need to substitute the password you entered above when you created the user in the Mongo shell.

You'll also need to specify your own MongoDB IP address and port instead of "server:port" as you see above.

The mongo.db.name property reflects the name of the database that you just created in the Mongo shell.

The mongo.user.collection property identifies the name of the collection that will keep user data.

If you're unfamiliar with MongoDB collections, they live up to their name. They're collections of documents that contain related data.

In this case, the users collection will contain user documents. Each document includes data about a specific user, such as his or her name, address, email, and password.

A MongoDB collection is basically a digital filing cabinet.

If you know a bit about relational database technology, you can think of collections as tables and documents as rows in tables.

Next, it's on to some Java code. Add a new class called MongoConfig in the com.careydevelopment.ecosystem.user.config package:

@Configuration
@EnableMongoRepositories(basePackages= {"com.careydevelopment.ecosystem.user.repository"})
public class MongoConfig extends AbstractMongoClientConfiguration {

    @Value("${mongo.connection.string}")
    private String mongoConnection;

    
    @Value("${mongo.db.name}")
    private String mongoDatabaseName;
    
    
    @Override
    protected String getDatabaseName() {
        return mongoDatabaseName;
    }
  
    
    @Override
    @Bean
    public MongoClient mongoClient() {
        String fullConnectionString = mongoConnection + "/" + mongoDatabaseName;
        
    	MongoClient client = MongoClients.create(fullConnectionString);
        return client;
    }
}

TL;DR - that wires you up for MongoDB integration.

First, note the @Value annotations. They're grabbing the values that you just created in application.properties.

The getDatabaseName() method is required by the parent class (actually the grandparent class). 

The mongoClient() method overrides what's in the parent class. But it also instantiates an object that becomes a Spring-managed bean. In other words, it can be autowired in other objects.

That MongoClient instance is useful when you need to handle complex transactions that involve aggregates. I'll cover aggregates in a future guide.

The @EnableMongoRepositories annotation at the top will cause Spring to the base package for Mongo repositories. You don't have any Mongo repositories yet, but stay tuned.

Now, when you start your Spring Boot application, you should see a couple of lines like this in the log:

Cluster created with settings {hosts=[server:port], mode=SINGLE, requiredClusterType=UNKNOWN, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500}

Opened connection [connectionId{localValue:1, serverValue:3566}] to server:port

That tells you that Spring Boot made a successful connection to your MongoDB instance.

Updating the Model

Now, you need to make a little update to the user model you created in the last guide.

Specifically, you need to add the @Document annotation to the beginning of the class declaration. Here's what the code looks like:

@Document(collection = "#{@environment.getProperty('mongo.user.collection')}")
public class User implements UserDetails {
...

The @Document annotation tells Spring that this User model will be persisted as a document in a MongoDB collection.

Which collection? Well that's specified with that bizarrely structured business in the parentheses. In a nutshell, that part references the mongo.user.collection property you added to the application.properties file earlier.

So the annotation is basically saying: "Convert this Java object to a MongoDB document and put it in the users collection."

The Repository

I alluded to a repository earlier. Now it's time to add one.

Start by creating a new package called com.careydevelopment.ecosystem.user.repository.

Inside that package add this interface:

public interface UserRepository extends MongoRepository<User, String> {

	public User findByUsername(String username);
	
	public User findByEmail(String email);
	
}

Yep, that's literally all you need to do. The Spring framework will handle the rest.

The interface extends MongoRepository. That tells the framework that you're using this interface to handle persistence with a MongoDB instance.

That MongoRepository interface also practices some type safety. The first type specified identifies the class that will get persisted with this interface. In this case, that's the User class.

The second type specified identifies the format of the ID that uniquely identifies each document in the MongoDB collection. In this case, it's a String.

Why is it a String instead of a number like a Long? Because MongoDB uses hexadecimal IDs (with numbers and letters) to identify documents.

The interface includes two methods: findByUsername() and findByEmail()

Those are the two methods you'll use to find users in the database. In other words, users can login with either a username or an email address.

We're user-friendly around these parts.

But the methods aren't implemented. How does Spring know what to do?

They're intuitively implemented based on their names. The Spring framework is smart enough to know that a method named findByUsername() will look for a document in the users collection with a specified username.

Remember, you actually have a field called username in the User class. That field will get persisted as a MongoDB document with that exact same field name.

Same goes for findByEmail(). You have a field in the User class called email. It will get persisted with that same name as well.

Spring just parses the method name you specified and figures out what to do. You can do that with any field name.

Updating the Service

If you remember how you did this in the previous guide (actually two guides ago), you'll recall that you hardcoded the user name and password in the JwtUserDetailsService class.

No more.

Now it's time to get the user data from the MongoDB collection. So here's what that class will look like now:

@Service
public class JwtUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);

        if (user != null) {         
            return user;
        } else {
            user = userRepository.findByEmail(username);
            
            if (user != null) {
                return user;
            } else {
                throw new UsernameNotFoundException("User not found with username: " + username);               
            }
        }
    }    
}

First of all, the class is annotated with @Service. That's because... it's a service.

Don't overthink this, folks.

Next, the code autowires the UserRepository type that you just created. That gives you access to those two methods you just added as well.

The class implements loadByUsername() a little differently than it did in the previous guide.

For starters, it assumes that the username is actually the username. And so it invokes findByUsername() from UserRepository.

Remember, though, you believe in user-friendly development. So you give people the opportunity to login with either a username or an email address.

So if findByUsername() returns nothing, then the code will say, "Okay, I didn't find a user when searching by username, now I'll try to find a user by email address."

That's why you see that if...else block in the code above with a second effort using findByEmail().

A Quick & Dirty Test

Thus far, you've written a lot of code to support integration with your MongoDB instance. But how do you know any of it works?

Well, you don't. Yet.

That's why you should perform what I'd like to call a quick and dirty test. You can do that with a listener.

I've already covered how to run initialization code in Spring Boot. I'll follow one of the options in that guide right here.

Create a new class in the .config package called ApplicationListenerInitialize. Populate it with code that looks like this:

@Component
public class ApplicationListenerInitialize implements ApplicationListener<ApplicationReadyEvent>  {
	
    @Autowired
    UserRepository userRepository;

    @Autowired
    PasswordEncoder encoder;
	
	
    public void onApplicationEvent(ApplicationReadyEvent event) {
        List<String> authorities = new ArrayList<String>();
        authorities.add("JWT_USER");      

    	User user = new User();
    	user.setAuthorityNames(authorities);
    	user.setCity("Detroit");
    	user.setEmail("darth@xmail.com");
    	user.setPhoneNumber("474-555-1212");
    	user.setState("MI");
    	user.setStreet1("123 Main St.");
    	user.setZip("36555");
    	user.setCountry("United States");
    	user.setFirstName("Darth");
    	user.setLastName("Vader");
    	user.setPassword(encoder.encode("thedarkside"));
    	user.setUsername("darth");

    	userRepository.insert(user);   	
    }
}

TL;DR - that code inserts a user into your MongoDB instance.

Note that when it comes to creating the password for the user account, you need to encode it with PasswordEncoder. That's because AuthenticationManagerBuilder uses that same encoder. Take a look at WebSecurityConfig if you want to see that in action.

Also, the code creates a List of authorities and adds just one String to it: JWT_USER. Take a look at the configure() method in WebSecurityConfig to see where that's used.

So how do you "run" this code? It's simple: just start your Spring Boot app!

Watch the log when you start Spring Boot. At the end you should see a line that looks like this:

Opened connection [connectionId{localValue:2, serverValue:3572}] to server:port

That's a good sign that you successfully created a user and inserted the user document into MongoDB.

But you can verify that using the Mongo shell.

Go back to that shell. Assuming you're still in the database you created, just enter the following command:

db.users.find({})

That command, by the way, means "show me every document in the users collection." You should only have one document in the collection now so the results won't crowd your screen.

The single document you see should look like this:

{ "_id" : ObjectId("5f78d8fbc1d3246ab4303f2b"), "firstName" : "Darth", "lastName" : "Vader", "street1" : "123 Main St.", "city" : "Detroit", "state" : "MI", "zip" : "36555", "email" : "darth@xmail.com", "phoneNumber" : "474-555-1212", "authorityNames" : [ "JWT_USER" ], "username" : "darth", "country" : "United States", "password" : "$2a$22$PkGlrfiFonJ.gyy3.Ix6V47P5YYMNFPuuIy80jfUpziWt93100fi", "_class" : "com.careydevelopment.ecosystem.user.model.User" }

Beautiful! Now you have a persisted user!

But before you do anything else: go into that initializer code and comment out the insert() line!

//userRepository.insert(user);

If you don't do that, then every time you restart Spring Boot, you'll add a new document to the collection.

Back to Angular

You probably thought I forgot about Angular. Nope.

Fortunately, you just need to make a modest change from the last guide.

Just go into into app.component.ts and update the credentials as follows:

    constructor(private authenticationService: AuthenticationService) {
        let user$ = authenticationService.login("darth", "thedarkside");

        user$.subscribe(
            (data: any) => console.log(data),
            err => console.error(err)
        );
    }

The only change there is the username and password.

Now, save the file and point your browser to: http://localhost:4200

Then, check the console in Developers tools. You should see something like this:

 

And that means you succeeded.

Wrapping It Up

Congratulations! You now have a user login solution that retrieves user data from a MongoDB collection!

It's up to you to take a look at the code and make it improvements where you see fit.

Feel free to add more fields to the User class. Create different authority names. Refactor the service class.

As always, make the code your own.

Also as always, have fun!