32
loading...
This website collects cookies to deliver better user experience
npm init -yes
npm install cors dotenv express express-rate-limit mongoose nodemon body-parser helmet morgan rate-limit-mongo
Dotenv: Dotenv is a zero-dependency module that loads environment variables from a .env file into process.env
cors: This module allows to relax the security applied to an API
express: Fast, unopinionated, minimalist web framework for node.
express-rate-limit: Basic IP rate-limiting middleware for Express. It is used to limit repeated requests to public APIs and/or endpoints such as password reset.
mongoose: It is an Object Data Modeling library for MongoDB and Node. js
nodemon: This module helps to develop node.js based applications by automatically restarting the application when file changes in the directory are detected.
body-parser: Node.js body parsing middleware.
helmet: Helmet.js fills in the gap between Node.js and Express.js by securing HTTP headers that are returned by Express applications.
morgan : HTTP request logger middleware for node.js
rate-limit-mongo : MongoDB store for the express-rate-limit middleware.
Import express module.
Import and configure dotenv module
Import helmet module.
Import morgan module.
Import CORS module
Use express() to initialize our app.
//src/index.js
const express = require('express');
// NOTE morgan is a logger
const morgan = require('morgan');
const helmet = require('helmet');
const cors = require('cors');
const mongoose = require('mongoose');
require('dotenv').config();
// app config
const app = express();
const express = require('express');
// NOTE morgan is a logger
const morgan = require('morgan');
const helmet = require('helmet');
const cors = require('cors');
const mongoose = require('mongoose');
require('dotenv').config();
const app = express();
const port = process.env.PORT || 4000;
app.use(morgan('common'));
app.use(helmet());
app.use(cors({
origin: process.env.CORS_ORIGIN,
}));
app.use(express.json());
app.get('/', (req, res) => {
res.json({
message: 'Hello There',
});
});
//src/index.js
const express = require('express');
// NOTE morgan is a logger
const morgan = require('morgan');
const helmet = require('helmet');
const cors = require('cors');
const mongoose = require('mongoose');
require('dotenv').config();
const app = express();
const DATABASE_CONNECTION = process.env.DATABASE_URL;
mongoose.connect(DATABASE_CONNECTION, {
useNewUrlParser: true,
newUnifiedTopology: true,
});
app.use(morgan('common'));
app.use(helmet());
app.use(cors({
origin: process.env.CORS_ORIGIN,
}));
app.use(express.json());
app.get('/', (req, res) => {
res.json({
message: 'Hello There',
});
});
const port = process.env.PORT || 4000;
app.listen(port, () => {
console.log(`Currently Listening at http://localhost:${port}`);
});
PORT=4000
DATABASE_URL=mongodb+srv://pramit:<password>@cluster0.8tw83.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
CORS_ORIGIN=http://localhost:3000
//middlewares.js
const notFound = (req, res, next) => {
const error = new Error(`Not Found - ${req.originalUrl}`);
res.status(404);
next(error);
};
const errorHandler = (error, req, res, next) => {
const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
res.status(statusCode);
res.json({
message: error.message,
stack: process.env.NODE_ENV === "production" ? "nope" : error.stack,
});
};
module.exports = {
notFound,
errorHandler,
};
//src/index.js
const express = require("express");
// NOTE morgan is a logger
const morgan = require("morgan");
const helmet = require("helmet");
const cors = require("cors");
const mongoose = require("mongoose");
require("dotenv").config();
const middlewares = require("./middlewares");
const app = express();
const DATABASE_CONNECTION = process.env.DATABASE_URL;
mongoose.connect(DATABASE_CONNECTION, {
useNewUrlParser: true,
newUnifiedTopology: true,
});
app.use(morgan("common"));
app.use(helmet());
app.use(
cors({
origin: process.env.CORS_ORIGIN,
})
);
app.use(express.json());
app.get("/", (req, res) => {
res.json({
message: "Hello There",
});
});
app.use(middlewares.notFound);
app.use(middlewares.errorHandler);
const port = process.env.PORT || 4000;
app.listen(port, () => {
console.log(`Currently Listening at http://localhost:${port}`);
});
//models/LogEntry.model.js
const mongoose = require("mongoose");
const { Schema } = mongoose;
const requiredNumber = {
type: Number,
required: true,
};
const logEntrySchema = new Schema(
{
title: {
type: String,
required: true,
},
description: String,
comments: String,
image: String,
rating: {
type: Number,
min: 0,
max: 10,
default: 0,
},
latitude: {
...requiredNumber,
min: -90,
max: 90,
},
longitude: {
...requiredNumber,
min: -180,
max: 180,
},
visitDate: {
required: true,
type: Date,
},
},
{
timestamps: true,
}
);
const LogEntry = mongoose.model("collections", logEntrySchema);
module.exports = LogEntry;
const { Router } = require("express");
const LogEntry = require("../models/LogEntry.model.js");
const { API_KEY } = process.env;
const router = Router();
router.get("/", async (req, res, next) => {
try {
const entries = await LogEntry.find();
res.json(entries);
} catch (error) {
next(error);
}
});
router.post("/", async (req, res, next) => {
try {
if (req.get("X-API-KEY") !== API_KEY) {
res.status(401);
throw new Error("Unauthorized Access");
}
const logEntry = new LogEntry(req.body);
const createdEntry = await logEntry.save();
res.json(createdEntry);
} catch (error) {
if (error.name === "ValidationError") {
res.status(422);
}
next(error);
}
});
module.exports = router;
//src/routes/logs.routes.js
const { Router } = require("express");
const LogEntry = require("../models/LogEntry.model.js");
const { API_KEY } = process.env;
const router = Router();
router.get("/", async (req, res, next) => {
try {
const entries = await LogEntry.find();
res.json(entries);
} catch (error) {
next(error);
}
});
router.post("/", async (req, res, next) => {
try {
if (req.get("X-API-KEY") !== API_KEY) {
res.status(401);
throw new Error("Unauthorized Access");
}
const logEntry = new LogEntry(req.body);
const createdEntry = await logEntry.save();
res.json(createdEntry);
} catch (error) {
if (error.name === "ValidationError") {
res.status(422);
}
next(error);
}
});
module.exports = router;
NODE_ENV=production
PORT=4000
DATABASE_URL=mongodb+srv://pramit:<password>@cluster0.8tw83.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
CORS_ORIGIN=http://localhost:3000
API_KEY=roadtripmapper
//src/index.js
const express = require("express");
// NOTE morgan is a logger
const morgan = require("morgan");
const helmet = require("helmet");
const cors = require("cors");
const mongoose = require("mongoose");
require("dotenv").config();
const middlewares = require("./middlewares");
const logs = require("./routes/logs.routes.js");
const app = express();
const DATABASE_CONNECTION = process.env.DATABASE_URL;
mongoose.connect(DATABASE_CONNECTION, {
useNewUrlParser: true,
newUnifiedTopology: true,
});
app.use(morgan("common"));
app.use(helmet());
app.use(
cors({
origin: process.env.CORS_ORIGIN,
})
);
app.use(express.json());
app.get("/", (req, res) => {
res.json({
message: "Hello There",
});
});
app.use("/api/logs", logs);
app.use(middlewares.notFound);
app.use(middlewares.errorHandler);
const port = process.env.PORT || 4000;
app.listen(port, () => {
console.log(`Currently Listening at http://localhost:${port}`);
});
npm i react-hook-form react-map-gl react-rating-stars-component react-responsive-animate-navbar
react-hook-form: Performant, flexible, and extensible forms library for React Hooks.
react-map-gl: react-map-gl is a suite of React components designed to provide a React API for Mapbox GL JS-compatible libraries
react-rating-stars-component: Simple star rating component for your React projects.
react-responsive-animate-navbar : simple, flexible & completely customizable responsive navigation bar component.
// components/RoadTripNav
import React from "react";
import * as ReactNavbar from "react-responsive-animate-navbar";
// import roadTripSvg from "../../assets/roadtrip.svg";
const RoadTripNav = () => {
return (
<ReactNavbar.ReactNavbar
color="rgb(25, 25, 25)"
logo="./logo.svg"
menu={[]}
social={[
{
name: "Twitter",
url: "https://twitter.com/pramit_armpit",
icon: ["fab", "twitter"],
},
]}
/>
);
};
export default RoadTripNav;
REACT_APP_MAPBOX_TOKEN= ************************************ // add token
//api/API.js
const API_URL = "http://localhost:4000";
// const API_URL = window.location.hostname === "localhost" ? "http://localhost:4000" : "https://road-trip-map-mern.herokuapp.com" ;
export async function listLogEntries() {
const response = await fetch(`${API_URL}/api/logs`);
// const json = await response.json();
return response.json();
}
export async function createLogEntries(entry) {
const api_key = entry.api_key;
delete entry.api_key;
const response = await fetch(`${API_URL}/api/logs`, {
method: "POST",
headers: {
"content-type": "application/json",
"X-API-KEY": api_key,
},
body: JSON.stringify(entry),
});
// const json = await response.json();
// return response.json();
let json;
if (response.headers.get("content-type").includes("text/html")) {
const message = await response.text();
json = {
message,
};
} else {
json = await response.json();
}
if (response.ok) {
return json;
}
const error = new Error(json.message);
error.response = json;
throw error;
}
// components/TripEntryForm.js
import React, { useState } from "react";
import { useForm } from "react-hook-form";
import { createLogEntries } from "../../api/API";
import "./TripEntryForm.css";
const TripEntryForm = ({ location, onClose }) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const { register, handleSubmit } = useForm();
const onSubmit = async (data) => {
try {
setLoading(true);
data.latitude = location.latitude;
data.longitude = location.longitude;
const created = await createLogEntries(data);
console.log(created);
onClose();
} catch (error) {
setError(error.message);
console.error(error);
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="trip-form">
{error ? <h3 className="error-message">{error}</h3> : null}
<label htmlFor="api_key">Enter Password</label>
<input
type="password"
name="api_key"
placeholder="For demo, password => {roadtripmap} "
required
ref={register}
/>
<label htmlFor="title">Title</label>
<input name="title" placeholder="Title" required ref={register} />
<label htmlFor="comments">Comments</label>
<textarea
name="comments"
placeholder="Comments"
rows={3}
ref={register}
></textarea>
<label htmlFor="description">Description</label>
<textarea
name="description"
placeholder="Describe your journey"
rows={4}
ref={register}
></textarea>
<label htmlFor="image">Image</label>
<input name="image" placeholder="Image URL" ref={register} />
<label htmlFor="rating">Rating (1 - 10)</label>
<input name="rating" type="number" min="0" max="10" ref={register} />
<label htmlFor="visitDate">Visit Date</label>
<input name="visitDate" type="date" required ref={register} />
<button disabled={loading}>
<span>{loading ? "Submitting..." : "Submit your Trip"}</span>
</button>
</form>
);
};
export default TripEntryForm;
//TripEntryForm.css
@import url("https://fonts.googleapis.com/css2?family=Fredoka+One&family=Poppins:ital,wght@0,200;0,400;1,200;1,300&family=Roboto:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap");
.trip-form label {
margin: 0.5rem 0;
display: block;
width: 100%;
color: rgb(255, 255, 255);
font-family: "Fredoka One", cursive;
}
.trip-form input {
margin: 0.5rem 0;
background-color: #2c2e41;
border-radius: 5px;
border: 0;
box-sizing: border-box;
color: rgb(255, 255, 255);
font-size: 12px;
height: 100%;
outline: 0;
padding: 10px 5px 10px 5px;
width: 100%;
font-family: "Fredoka One", cursive;
}
.trip-form textarea {
margin: 0.5rem 0;
background-color: #2c2e41;
border-radius: 5px;
border: 0;
box-sizing: border-box;
color: rgb(255, 255, 255);
font-size: 12px;
height: 100%;
outline: 0;
padding: 10px 5px 10px 5px;
width: 100%;
font-family: "Fredoka One", cursive;
}
.error-message {
color: red;
}
.trip-form button {
background-color: #fb5666;
border-radius: 12px;
border: 0;
box-sizing: border-box;
color: #eee;
cursor: pointer;
font-size: 18px;
height: 50px;
margin-top: 38px;
outline: 0;
text-align: center;
width: 100%;
}
button span {
position: relative;
z-index: 2;
}
button:after {
position: absolute;
content: "";
top: 0;
left: 0;
width: 0;
height: 100%;
transition: all 2.35s;
}
button:hover {
color: #fff;
}
button:hover:after {
width: 100%;
}
.small_description {
font-size: 60px;
}
//src/app.js
import * as React from "react";
import { useState, useEffect } from "react";
import ReactMapGL, { Marker, Popup } from "react-map-gl";
import { listLogEntries } from "./api/API";
import MapPinLogo from "./assets/mapperPin.svg";
import MarkerPopup from "./assets/MarkerPopup.svg";
import TripEntryForm from "./components/TripEntryForm";
import ReactStars from "react-rating-stars-component";
import RoadTripNav from "./components/RoadTripNav/RoadTripNav";
const App = () => {
const [logEntries, setLogEntries] = useState([]);
const [showPopup, setShowPopup] = useState({});
const [addEntryLocation, setAddEntryLocation] = useState(null);
const [viewport, setViewport] = useState({
width: "100vw",
height: "100vh",
latitude: 27.7577,
longitude: 85.3231324,
zoom: 7,
});
const getEntries = async () => {
const logEntries = await listLogEntries();
setLogEntries(logEntries);
console.log(logEntries);
};
useEffect(() => {
getEntries();
}, []);
const showMarkerPopup = (event) => {
console.log(event.lngLat);
const [longitude, latitude] = event.lngLat;
setAddEntryLocation({
longitude,
latitude,
});
};
return (
<>
<RoadTripNav />
<ReactMapGL
{...viewport}
mapStyle="mapbox://styles/pramitmarattha/ckiovge5k3e7x17tcmydc42s3"
mapboxApiAccessToken={process.env.REACT_APP_MAPBOX_TOKEN}
onViewportChange={(nextViewport) => setViewport(nextViewport)}
onDblClick={showMarkerPopup}
>
{logEntries.map((entry) => (
<React.Fragment key={entry._id}>
<Marker latitude={entry.latitude} longitude={entry.longitude}>
<div
onClick={() =>
setShowPopup({
// ...showPopup,
[entry._id]: true,
})
}
>
<img
className="map-pin"
style={{
width: `${5 * viewport.zoom}px`,
height: `${5 * viewport.zoom}px`,
}}
src={MapPinLogo}
alt="Map Pin Logo"
/>
</div>
</Marker>
{showPopup[entry._id] ? (
<Popup
latitude={entry.latitude}
longitude={entry.longitude}
closeButton={true}
closeOnClick={false}
dynamicPosition={true}
onClose={() => setShowPopup({})}
anchor="top"
>
<div className="popup">
<ReactStars
count={10}
value={entry.rating}
size={29}
activeColor="#ffd700"
/>
<div className="popup_image">
{entry.image && <img src={entry.image} alt={entry.title} />}
</div>
<h3>{entry.title}</h3>
<p>{entry.comments}</p>
<small>
Visited :{" "}
{new Date(entry.visitDate).toLocaleDateString("en-US", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
})}
</small>
<p>Ratings: {entry.rating}</p>
<div className="small_description">{entry.description}</div>
</div>
</Popup>
) : null}
</React.Fragment>
))}
{addEntryLocation ? (
<>
<Marker
latitude={addEntryLocation.latitude}
longitude={addEntryLocation.longitude}
>
<div>
<img
className="map-pin"
style={{
width: `${8 * viewport.zoom}px`,
height: `${8 * viewport.zoom}px`,
}}
src={MarkerPopup}
alt="Map Pin Logo"
/>
</div>
{/* <div style={{color:"white"}}>{entry.title}</div> */}
</Marker>
<Popup
latitude={addEntryLocation.latitude}
longitude={addEntryLocation.longitude}
closeButton={true}
closeOnClick={false}
dynamicPosition={true}
onClose={() => setAddEntryLocation(null)}
anchor="top"
>
<div className="popup">
<TripEntryForm
onClose={() => {
setAddEntryLocation(null);
getEntries();
}}
location={addEntryLocation}
/>
</div>
</Popup>
</>
) : null}
</ReactMapGL>
</>
);
};
export default App;
/* styles/index.css */
@import url("https://fonts.googleapis.com/css2?family=Fredoka+One&family=Poppins:ital,wght@0,200;0,400;1,200;1,300&family=Roboto:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap");
body {
margin: 0;
font-family: "Fredoka One", cursive;
height: 100vh;
width: 100vw;
overflow: hidden;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
.map-pin {
position: absolute;
transform: translate(-50%, -100%);
z-index: -1;
}
.popup {
width: 20vw;
height: auto;
padding: 1rem;
background-color: #8661d1;
border-radius: 5px;
z-index: 999;
}
.popup img {
width: 40%;
height: auto;
border-radius: 5%;
justify-content: center;
align-items: center;
margin: 0 auto;
padding-top: 1rem;
}
.popup_image {
display: flex;
justify-content: center;
align-items: center;
}
.small_description {
font-size: 1.5rem;
color: #fff;
border-radius: 5px;
z-index: 999;
}
button {
border: none;
color: #fa5252;
padding-right: 1rem;
border-radius: 50%;
font-size: 4rem;
margin-top: 0.2rem;
height: auto;
cursor: pointer;
}