├── .gitignore ├── config ├── config.env └── db.js ├── routes └── stores.js ├── utils └── geocoder.js ├── package.json ├── README.md ├── server.js ├── public ├── js │ ├── add.js │ └── map.js ├── add.html └── index.html ├── controllers └── stores.js └── models └── Store.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /config/config.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | PORT=5000 3 | 4 | MONGO_URI=XXXXX 5 | 6 | GEOCODER_PROVIDER=mapquest 7 | GEOCODER_API_KEY=XXXXXX -------------------------------------------------------------------------------- /routes/stores.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { getStores, addStore } = require('../controllers/stores'); 3 | 4 | const router = express.Router(); 5 | 6 | router 7 | .route('/') 8 | .get(getStores) 9 | .post(addStore); 10 | 11 | module.exports = router; 12 | -------------------------------------------------------------------------------- /utils/geocoder.js: -------------------------------------------------------------------------------- 1 | const NodeGeocoder = require('node-geocoder'); 2 | 3 | const options = { 4 | provider: process.env.GEOCODER_PROVIDER, 5 | httpAdapter: 'https', 6 | apiKey: process.env.GEOCODER_API_KEY, 7 | formatter: null 8 | }; 9 | 10 | const geocoder = NodeGeocoder(options); 11 | 12 | module.exports = geocoder; 13 | -------------------------------------------------------------------------------- /config/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const connectDB = async () => { 4 | try { 5 | const conn = await mongoose.connect(process.env.MONGO_URI, { 6 | useNewUrlParser: true, 7 | useCreateIndex: true, 8 | useFindAndModify: false, 9 | useUnifiedTopology: true 10 | }); 11 | 12 | console.log(`MongoDB Connected: ${conn.connection.host}`); 13 | } catch (error) { 14 | console.error(error); 15 | process.exit(1); 16 | } 17 | }; 18 | 19 | module.exports = connectDB; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "store_locator_api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "dev": "nodemon server.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "cors": "^2.8.5", 15 | "dotenv": "^8.2.0", 16 | "express": "^4.17.1", 17 | "mongoose": "^5.8.3", 18 | "node-geocoder": "^3.25.0" 19 | }, 20 | "devDependencies": { 21 | "nodemon": "^2.0.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Store Locator 2 | 3 | > Node/Express/Mongo API with GeoJSON location field for store locations. Simple vanilla JS frontend using the Mapbox Library 4 | 5 | ## Quick Start 6 | 7 | Add your MONGO_URI and GEOCODER_API_KEY to the "config/config.env" file. 8 | 9 | ```bash 10 | # Install dependencies 11 | npm install 12 | 13 | # Serve on localhost:5000 14 | npm run dev (nodemon) 15 | or 16 | npm start 17 | 18 | # Routes 19 | GET /api/v1/stores # Get Stores 20 | 21 | POST /api/v1/stores # Add Store 22 | body { storeId: "0001", address: "10 main st Boston MA" } 23 | ``` 24 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | const dotenv = require('dotenv'); 4 | const cors = require('cors'); 5 | const connectDB = require('./config/db'); 6 | 7 | // load env vars 8 | dotenv.config({ path: './config/config.env' }); 9 | 10 | // Connect to database 11 | connectDB(); 12 | 13 | const app = express(); 14 | 15 | // Body parser 16 | app.use(express.json()); 17 | 18 | // Enable cors 19 | app.use(cors()); 20 | 21 | // Set static folder 22 | app.use(express.static(path.join(__dirname, 'public'))); 23 | 24 | // Routes 25 | app.use('/api/v1/stores', require('./routes/stores')); 26 | 27 | const PORT = process.env.PORT || 5000; 28 | 29 | app.listen(PORT, () => 30 | console.log(`Server running in ${process.env.NODE_ENV} mode on port ${PORT}`) 31 | ); 32 | -------------------------------------------------------------------------------- /public/js/add.js: -------------------------------------------------------------------------------- 1 | const storeForm = document.getElementById('store-form'); 2 | const storeId = document.getElementById('store-id'); 3 | const storeAddress = document.getElementById('store-address'); 4 | 5 | // Send POST to API to add store 6 | async function addStore(e) { 7 | e.preventDefault(); 8 | 9 | if (storeId.value === '' || storeAddress.value === '') { 10 | alert('Please fill in fields'); 11 | } 12 | 13 | const sendBody = { 14 | storeId: storeId.value, 15 | address: storeAddress.value 16 | }; 17 | 18 | try { 19 | const res = await fetch('/api/v1/stores', { 20 | method: 'POST', 21 | headers: { 22 | 'Content-Type': 'application/json' 23 | }, 24 | body: JSON.stringify(sendBody) 25 | }); 26 | 27 | if (res.status === 400) { 28 | throw Error('Store already exists!'); 29 | } 30 | 31 | alert('Store added!'); 32 | window.location.href = '/index.html'; 33 | } catch (err) { 34 | alert(err); 35 | return; 36 | } 37 | } 38 | 39 | storeForm.addEventListener('submit', addStore); 40 | -------------------------------------------------------------------------------- /controllers/stores.js: -------------------------------------------------------------------------------- 1 | const Store = require('../models/Store'); 2 | 3 | // @desc Get all stores 4 | // @route GET /api/v1/stores 5 | // @access Public 6 | exports.getStores = async (req, res, next) => { 7 | try { 8 | const stores = await Store.find(); 9 | 10 | return res.status(200).json({ 11 | success: true, 12 | count: stores.length, 13 | data: stores 14 | }); 15 | } catch (err) { 16 | console.error(err); 17 | res.status(500).json({ error: 'Server error' }); 18 | } 19 | }; 20 | 21 | // @desc Create a store 22 | // @route POST /api/v1/stores 23 | // @access Public 24 | exports.addStore = async (req, res, next) => { 25 | try { 26 | const store = await Store.create(req.body); 27 | 28 | return res.status(201).json({ 29 | success: true, 30 | data: store 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | if (err.code === 11000) { 35 | return res.status(400).json({ error: 'This store already exists' }); 36 | } 37 | res.status(500).json({ error: 'Server error' }); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /models/Store.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const geocoder = require('../utils/geocoder'); 3 | 4 | const StoreSchema = new mongoose.Schema({ 5 | storeId: { 6 | type: String, 7 | required: [true, 'Please add a store ID'], 8 | unique: true, 9 | trim: true, 10 | maxlength: [10, 'Store ID must be less than 10 chars'] 11 | }, 12 | address: { 13 | type: String, 14 | required: [true, 'Please add an address'] 15 | }, 16 | location: { 17 | type: { 18 | type: String, 19 | enum: ['Point'] 20 | }, 21 | coordinates: { 22 | type: [Number], 23 | index: '2dsphere' 24 | }, 25 | formattedAddress: String 26 | }, 27 | createdAt: { 28 | type: Date, 29 | default: Date.now 30 | } 31 | }); 32 | 33 | // Geocode & create location 34 | StoreSchema.pre('save', async function(next) { 35 | const loc = await geocoder.geocode(this.address); 36 | this.location = { 37 | type: 'Point', 38 | coordinates: [loc[0].longitude, loc[0].latitude], 39 | formattedAddress: loc[0].formattedAddress 40 | }; 41 | 42 | // Do not save address 43 | this.address = undefined; 44 | next(); 45 | }); 46 | 47 | module.exports = mongoose.model('Store', StoreSchema); 48 | -------------------------------------------------------------------------------- /public/add.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 13 | 14 |