Using NoSQL
Learn how to use NoSQL databases in the JAQ Stack ecosystem for flexible and scalable data storage.
Overview
NoSQL databases provide the data persistence layer for JAQ Stack, offering flexibility, scalability, and performance for modern applications.
Supported Databases
JAQ Stack supports multiple NoSQL databases:
- MongoDB - Document database (Primary)
- CouchDB - Document database
- Redis - Key-value store
- Cassandra - Wide-column store
Getting Started
Prerequisites
- Docker (recommended)
- Java 17+ (for Spring Boot integration)
- Node.js 18+ (for JavaScript/TypeScript integration)
Installation
Using Docker (Recommended)
# MongoDB
docker run -d --name mongodb \
-p 27017:27017 \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=password \
mongo:latest
# Redis
docker run -d --name redis \
-p 6379:6379 \
redis:latest
# CouchDB
docker run -d --name couchdb \
-p 5984:5984 \
-e COUCHDB_USER=admin \
-e COUCHDB_PASSWORD=password \
couchdb:latest
Manual Installation
# MongoDB (Ubuntu/Debian)
wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
# Redis (Ubuntu/Debian)
sudo apt update
sudo apt install redis-server
Configuration
Spring Boot Configuration
# application.properties
# MongoDB Configuration
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=jaqstack
spring.data.mongodb.username=admin
spring.data.mongodb.password=password
spring.data.mongodb.authentication-database=admin
# Redis Configuration
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.timeout=2000ms
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
Java Configuration
// MongoDB Configuration
@Configuration
@EnableMongoRepositories
public class MongoConfig {
@Value("${spring.data.mongodb.host}")
private String host;
@Value("${spring.data.mongodb.port}")
private int port;
@Value("${spring.data.mongodb.database}")
private String database;
@Bean
public MongoClient mongoClient() {
return MongoClients.create(
MongoClientSettings.builder()
.applyToClusterSettings(builder ->
builder.hosts(Arrays.asList(new ServerAddress(host, port))))
.build()
);
}
@Bean
public MongoTemplate mongoTemplate() {
return new MongoTemplate(mongoClient(), database);
}
}
Data Models
Document Structure
// User Document
@Document(collection = "users")
public class User {
@Id
private String id;
@Field("name")
private String name;
@Field("email")
private String email;
@Field("profile")
private UserProfile profile;
@Field("created_at")
private LocalDateTime createdAt;
@Field("updated_at")
private LocalDateTime updatedAt;
// Constructors, getters, setters
}
// Embedded Document
@Document
public class UserProfile {
private String bio;
private String avatar;
private List<String> skills;
private Map<String, Object> preferences;
}
Repository Pattern
@Repository
public interface UserRepository extends MongoRepository<User, String> {
// Custom query methods
List<User> findByNameContainingIgnoreCase(String name);
List<User> findByEmail(String email);
@Query("{ 'profile.skills': { $in: ?0 } }")
List<User> findBySkillsIn(List<String> skills);
@Query("{ 'created_at': { $gte: ?0, $lte: ?1 } }")
List<User> findByCreatedAtBetween(LocalDateTime start, LocalDateTime end);
// Aggregation queries
@Aggregation("{ $group: { _id: '$role', count: { $sum: 1 } } }")
List<RoleCount> countUsersByRole();
}
Best Practices
Data Modeling
- Denormalization: Store related data together for better performance
- Embedding vs Referencing: Embed for 1:1 or 1:few relationships
- Indexing: Create indexes on frequently queried fields
- Schema Design: Design for your query patterns
Performance Optimization
// Index Configuration
@Document(collection = "users")
@CompoundIndex(def = "{'name': 1, 'email': 1}")
@CompoundIndex(def = "{'created_at': -1}")
public class User {
// Document fields
}
// Query Optimization
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public List<User> findUsersOptimized(String name) {
// Use projection to limit returned fields
Query query = new Query();
query.addCriteria(Criteria.where("name").regex(name, "i"));
query.fields().include("name", "email", "profile.avatar");
return mongoTemplate.find(query, User.class);
}
}
Caching with Redis
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.build();
}
}
// Using Cache
@Service
public class UserService {
@Cacheable(value = "users", key = "#id")
public User findById(String id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
@CacheEvict(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
}
Data Migration
@Component
public class DataMigrationService {
@Autowired
private MongoTemplate mongoTemplate;
@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
migrateUserData();
}
private void migrateUserData() {
// Example migration: Add new field to existing documents
Query query = new Query();
Update update = new Update().set("migrated", true);
UpdateResult result = mongoTemplate.updateMulti(query, update, User.class);
log.info("Migrated {} user documents", result.getModifiedCount());
}
}
Monitoring and Maintenance
Health Checks
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
@Autowired
private MongoTemplate mongoTemplate;
@Override
public Health health() {
try {
mongoTemplate.getCollection("users").countDocuments();
return Health.up()
.withDetail("database", "MongoDB")
.withDetail("status", "Connected")
.build();
} catch (Exception e) {
return Health.down()
.withDetail("database", "MongoDB")
.withDetail("error", e.getMessage())
.build();
}
}
}
Backup and Restore
# MongoDB Backup
mongodump --host localhost:27017 --db jaqstack --out /backup/jaqstack
# MongoDB Restore
mongorestore --host localhost:27017 --db jaqstack /backup/jaqstack
# Redis Backup
redis-cli BGSAVE
Troubleshooting
Common Issues
- Connection Timeout: Check network connectivity and firewall settings
- Memory Usage: Monitor memory consumption and adjust cache settings
- Index Performance: Analyze slow queries and optimize indexes
- Data Consistency: Implement proper transaction handling
Performance Monitoring
@Component
public class DatabaseMetrics {
@Autowired
private MongoTemplate mongoTemplate;
@Scheduled(fixedRate = 60000) // Every minute
public void collectMetrics() {
// Collect database metrics
Document stats = mongoTemplate.getDb().runCommand(new Document("dbStats", 1));
log.info("Database stats: {}", stats);
}
}
Getting Help
- Check the GitHub Issues
- Join our Discord Community
- Read the MongoDB Documentation
- Read the Redis Documentation
Next Steps
- Using Java - Backend integration
- Using Angular - Frontend development
- DevOps - Deployment and operations