Sample application using MongoDB of Spring Boot

This is a reprint of the README of this repository.

This is an example of a business application that uses Spring Boot on the server side and AngularJS on the client side to access the backend MongoDB.

The database has a collection of brand, model, car and salesperformance. It will have the ability to add, update, delete, and search for these collections, as well as the ability to graph sales performance.

This document is a note on the key technical themes needed to understand the application. To speed up your understanding, it's a good idea to go through Spring's Guide Page Tutorial.

*Caution Not all features work properly at this time as this project has not finished developing features. *

I will move it for the time being

Install MongoDB and start mongod in advance. Leave the authentication function of mongodb unconfigured. (It is in a state where it is installed and started without setting anything)

Git clone this project. Create an appropriate directory and execute the following command in that directory.

$ git clone https://github.com/kazz12211/simple-mongo-crud-app.git

Execute the following command in the same directory to start the application.

$ ./mvnw spring-boot:run

Access the following URL from your browser.

http://localhost:8080

Development environment

Spring related

Maven project

This application is a Spring Boot Maven project. See pom.xml for dependent libraries.

RestController

Generate RestController by @RestController annotation. It seems common in CRUD applications to map data inserts, updates, deletes, and searches to HTTP requests POST, PUT, DELETE, and GET, respectively.

	package jp.tsubakicraft.mongocrud.controller;

	import java.util.List;
	
	import org.springframework.beans.factory.annotation.Autowired;
	import org.springframework.data.domain.Page;
	import org.springframework.data.domain.PageRequest;
	import org.springframework.data.domain.Pageable;
	import org.springframework.data.domain.Sort;
	import org.springframework.web.bind.annotation.RequestBody;
	import org.springframework.web.bind.annotation.RequestMapping;
	import org.springframework.web.bind.annotation.RequestMethod;
	import org.springframework.web.bind.annotation.RequestParam;
	import org.springframework.web.bind.annotation.RestController;
	
	import jp.tsubakicraft.mongocrud.model.Brand;
	import jp.tsubakicraft.mongocrud.service.BrandRepository;
	
	@RestController
	public class BrandController {
	
		@Autowired
		private BrandRepository repo;
	
		@RequestMapping(value = "/api/brands/listAll", method = RequestMethod.GET)
		public List<Brand> listAll() {
			Sort sort = new Sort(Sort.Direction.ASC, "name");
			return repo.findAll(sort);
		}
	
		@RequestMapping(value = "/api/brands", method = RequestMethod.GET)
		public Page<?> listBrands(@RequestParam(value = "page", required = true) int page,
				@RequestParam(value = "limit", required = true) int limit,
				@RequestParam(value = "sortColumn", required = true) String column,
				@RequestParam(value = "sortDir", required = true) String dir) {
			Sort sort = new Sort(
					new Sort.Order("asc".equalsIgnoreCase(dir) ? Sort.Direction.ASC : Sort.Direction.DESC, column));
			Pageable pageRequest = new PageRequest(page, limit, sort);
			Page<Brand> p = repo.findAll(pageRequest);
			return p;
		}
	
		@RequestMapping(value = "/api/brands", method = RequestMethod.PUT)
		public Brand updateBrand(@RequestBody Brand brand) {
			Brand b = repo.findOne(brand.id);
			if (b != null) {
				b.name = brand.name;
				repo.save(b);
			}
			return b;
		}
	
		@RequestMapping(value = "/api/brands", method = RequestMethod.DELETE)
		public Brand deleteBrand(@RequestBody Brand brand) {
			repo.delete(brand.id);
			return brand;
		}
	
		@RequestMapping(value = "/api/brands", method = RequestMethod.POST)
		public Brand createBrand(@RequestBody Brand brand) {
			Brand b = new Brand();
			b.name = brand.name;
			repo.save(b);
			return b;
		}
	}

This application implements pagination function using ui-bootstrap in UI, but to search for objects on a page-by-page basis, use PageRequest. For example, to search the 11th to 20th Brand objects, call findAll () of PagingAndSortingRepository with PageRequest as an argument as follows.

	int page = 1;
	int size = 10;
	Pageable pageRequest = new PageRequest(page, size);
	Page<Brand> page = repo.findAll(pageRequest);

Dealing with Page Not Found errors when using Natural routes

	package jp.tsubakicraft.mongocrud.controller;
	
	import org.springframework.stereotype.Controller;
	import org.springframework.web.bind.annotation.RequestMapping;
	
	@Controller
	public class ErrorHandler {
		@RequestMapping(value = "/{[path:[^\\.]*}")
		public String redirect() {
		  return "forward:/";
		}
	}

AngularJS related

HTML template componentization (using $ routeProvider)

	in app.js
	
	var app = angular.module("app", ['ngRoute', 'ngDialog', 'ui.bootstrap', 'chart.js']);
	
	app.config(['$routeProvider', function($routeProvider) {
			
	    $routeProvider
		.when("/", {
			controller: 'home_controller',
			templateUrl: 'views/home.html'
		})
		.when("/brand/", {
			controller: 'brand_controller',
			templateUrl: 'views/brands.html'
		})
		.when("/newbrand/", {
			controller: 'brand_controller',
			templateUrl: 'views/newBrand.html'
		})
		.when("/model/", {
			controller: 'model_controller',
			templateUrl: 'views/models.html'
		})
		.when("/newmodel/", {
			controller: 'model_controller',
			templateUrl: 'views/newModel.html'
		})
		.when("/car/", {
			controller: 'car_controller',
			templateUrl: 'views/cars.html'
		})
		.when("/newcar/", {
			controller: 'car_controller',
			templateUrl: 'views/newCar.html'
		})
		.when("/sales/", {
			controller: 'sales_controller',
			templateUrl: 'views/sales.html'
		})
		.when("/newsales/", {
			controller: 'sales_controller',
			templateUrl: 'views/newSales.html'
		})
		.otherwise({
			redirectTo: "/"
		});
	}]);
	app.config(['$locationProvider', function($locationProvider) {
		$locationProvider.html5Mode(true);
	}]);

REST API call (using $ http)

For example, if you want to make a GET request to the / api / brands root of BrandController (REST Controller), use $ http.get (). (See brand_controller.js)

	app.controller("brand_controller", function($scope, $http, $location, $q, ngDialog) {
		$scope.brands = [];
	....
	....
		$scope.listBrands = function() {
			$http.get("/api/brands", {params: {page: $scope.page, limit: $scope.limit, sortColumn: $scope.sortColumn, sortDir: $scope.sortDir}}).then(function(response) {
				//I was able to receive the data normally
				$scope.brands = response.data;
				....
				....
			}, function(error) {
				//HTTP request failed
				....
			});
		};
	....
	....
	
		$scope.listBrands();
	});

Form input check

Validation can be done in the HTML template, but in this application it is done in the controller. (See brand_controller.js)

	....
	....
	$scope.createBrand = function() {
		if(!$scope.validateForm()) {
			$http.post("/api/brands", $scope.brand).then(function(response) {
		        $scope.show = true;
		        $scope.hide = true;
		        $scope.hideObj = false;
		        $scope.showObj = false;
		        $scope.brandId = "";
		    	$location.path("/brand");
			}, function(error) {
				$scope.error = error;
			});
		}
	};
	....
	....
	$scope.validateForm = function() {
		$scope.validationMessages = [];
		if($scope.brand.name == null || $scope.brand.name == null) {
			$scope.validationMessages.push("Name is required.");
		}
		$scope.hasErrors =  $scope.validationMessages.length > 0;
		return $scope.hasErrors;
	};

On the HTML template side, prepare a block to be displayed when there is an error (hasErrors of $ scope is true).

	<div class="panel panel-default">
		<div class="panel-heading">ADD A BRAND</div>
		<form name="brand-form">
			<div ng-show="hasErrors">
				<div class="alert alert-danger" role="alert">
					<div ng-repeat="message in validationMessages">
						<strong>{{message}}</strong></br/>
					</div>
				</div>
			</div>
			<div class="form-group">
				<label for="brand-name">Name</label> <input name="brand-name"
					type="text" class="form-control" ng-model="brand.name" required>
			</div>
			<button class="btn btn-primary" type="submit" ng-click="createBrand()">
				<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
				Save
			</button>
	
			<button class="btn btn-default" ng-click="linkTo('/brand/')">
				<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
				Cancel
			</button>
		</form>
	</div>

Pagination (using ui-bootstrap)

The ui-bootstrap page (https://angular-ui.github.io/bootstrap/) explains how to do this.

Table column sort

Code on the controller side. Specify the name of the column to sort and the sorting method. (For this application, there are two sorting methods, ASC and DESC) The sort () function is called from the HTML template with the column name as an argument. If the column passed as an argument is the same column as the previous one, switch between ascending and descending order, and if the column is different from the previous one, set it in ascending order and update the column name. Finally, search the DB again. (Since it is a paginated search, the traffic to the database generated by one search is small, so we are re-searching, but if the number of searches is large, it may be better to sort in memory)

	app.controller("model_controller", function($scope, $http, $location, $q, ngDialog) {
		....
		....
	
		$scope.sortColumn = "name";
		$scope.sortDir = "ASC";
		....
		....
		$scope.sort = function(column) {
			if($scope.sortColumn == column) {
				$scope.sortDir = $scope.sortDir == "ASC" ? "DESC" : "ASC";
			} else {
				$scope.sortDir = "ASC";
				$scope.sortColumn = column;
			}
			$scope.listModels();
		};
	
	});

On the HTML template side, specify the controller function to be executed when the mouse click is performed on the column header.

	<th class="col-xs-3 col-ms-3 col-md-3 col-lg-4 sortableTableColumn" ng-click="sort('name')">Name</th>

If you use a library such as DataTable.js, which has functions such as pagination and sorting of data to be displayed in the table, it can be done more easily, so when developing an application. Please consider.

BootStrap related

Bootstrap CSS override

It's not limited to Bootstrap CSS, but it's a way to override CSS. The following code overrides Bootstrap's navigation bar style. Set the part of the HTML file that is loading CSS to load after Bootstrap CSS.

	.navbar {
		margin-bottom: 1px;
	        border-radius: 0px;
	}
	
	.navbar-inverse {
		background-color: rgb(12, 140, 213);
		border-color: rgb(12,140,213);	
	}
	
	.navbar-inverse .navbar-brand {
		color: #fff;
	}
	
	.navbar-inverse .navbar-nav>li>a {
		color: #fff;
	}

Mongo Repository related

MongoRepository is a sub-interface of PagingAndSortingRepository that has data insertion, update, deletion, search and pagination functions.

MongoDB configuration

The code that sets the MongoDB database selection.

	package jp.tsubakicraft.mongocrud.config;
	
	import org.springframework.context.annotation.Configuration;
	import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
	
	import com.mongodb.Mongo;
	import com.mongodb.MongoClient;
	
	@Configuration
	public class MongoConfiguration extends AbstractMongoConfiguration {
	
		@Override
		protected String getDatabaseName() {
			return "simplecrud";
		}
	
		@Override
		public Mongo mongo() throws Exception {
			return new MongoClient("127.0.0.1", 27017);
		}
	
	}

MongoDB entity

Examples of Brand and Model entities. The Model entity references the Brand entity with the @DBRef annotation. In this case, searching for Model will also search for and combine the referenced Brands.

jp.tsubakicraft.mongocrud.model.Brand.java

	package jp.tsubakicraft.mongocrud.model;
	
	import org.springframework.data.annotation.Id;
	import org.springframework.data.mongodb.core.mapping.Document;
	
	
	@Document(collection="brand")
	public class Brand {
	
		@Id public String id;
		public String name;
		
		public Brand(String id) {
			this.id = id;
		}
		
		public Brand() {
		}
	}

jp.tsubakicraft.mongocrud.model.Model.java

	 package jp.tsubakicraft.mongocrud.model;
	
	
	import org.springframework.data.annotation.Id;
	import org.springframework.data.mongodb.core.mapping.DBRef;
	import org.springframework.data.mongodb.core.mapping.Document;
	
	
	@Document(collection="model")
	public class Model {
		
		@Id public String id;
		public String name;
		@DBRef public Brand brand;
		
		public Model() {
			
		}
		
		public Model(String id) {
			this.id = id;
		}
	}

Extension of Mongo Repository

How to specify DBRef properties in search criteria.

	@Query(value="{brand.$id : ?0}")
	public List<Model> findByBrandId(ObjectId brandId);

How to get the number of collections. Set count = true as a parameter of @Query annotation.

	@Query(value="{brand.$id : ?0}", count=true)
	public Long countBrandModel(ObjectId brandId);

Pagination with MongoRepository

Call a method of MongoRepository using PageRequest.

	@Autowired
	private BrandRepository repo;
	...
	@RequestMapping(value="/api/brands", method=RequestMethod.GET)
	public Page<Brand> listBrands(@RequestParam(value="page", required=true) int page, @RequestParam(value="limit", required=true) int limit) {
		Pageable pageRequest = new PageRequest(page, limit);
		return repo.findAll(pageRequest);
	}

Recommended Posts

Sample application using MongoDB of Spring Boot
Python: Application of image recognition using CNN
This is a sample of function application in dataframe.
Web application using Bottle (1)
Example of using lambda
Application of Python 3 vars