Make a Community Events Calendar

Milecia

Raising awareness of things going on in your community is a great way to make people feel more connected and involved in what's going on around them. A community calendar where people can add events can help with that.

We're going to build a calendar that uses Flask on the back-end to interact with a Postgres database and React on the front-end. Users will be able to upload event details, see which days have events, and more.

Setting up the project

We're going to need a root folder to hold the front-end and back-end of our project, so create a new folder called events-calendar. Inside this folder, add a new subfolder called server. We'll make the Flask app in the server folder.

Now let's set up the local Postgres instance. You can download it here. Make sure you take note of your username and password because we'll need them in the connection string for the back-end. That's all the base setup we need and now we can start making the Flask app.

Working in the Flask environment

Since the back-end will use a Python framework, you'll need to create a virtual environment before we start installing packages. To do that, open your terminal, go to the server directory, and run the following commands:

1$ python3 -m venv .venv
2$ source .venv/bin/activate

Next, we'll install the Flask package and some others with the following command:

1$ pip install flask flask_sqlalchemy flask_cors flask_migrate

With the environment set up and all of the packages installed, we're ready to create some files for our server.

Making the Flask API

Inside the server folder, add a new file called api.py. This is the file that will hold the different endpoints our front-end will call. Open this new file and add the following code.

1# api.py
2import json
3from flask import Flask, jsonify, request
4from flask_sqlalchemy import SQLAlchemy
5from flask_migrate import Migrate
6from flask_cors import CORS
7
8app = Flask(__name__)
9
10CORS(app)
11
12app.config[
13 "SQLALCHEMY_DATABASE_URI"
14] = "postgresql://username:password@localhost:5432/calendar"
15
16db = SQLAlchemy(app)
17
18migrate = Migrate(app, db)

This brings in all of the packages we need and it creates the application. We're applying the CORS package to the app so that we can call the endpoint from a different domain. Then we're connecting the app to our Postgres database and getting it ready to migrate the schema. Make sure you put in your username and password for that connection string.

Database schema

We can add the schema for the one table we need below our app setup. Below the existing code, add the following.

1# api.py
2
3...
4class EventsModel(db.Model):
5 __tablename__ = "events"
6
7 id = db.Column(db.Integer, primary_key=True)
8 title = db.Column(db.String())
9 date = db.Column(db.Date())
10 time = db.Column(db.Time())
11 location = db.Column(db.String())
12 description = db.Column(db.String())
13
14 def __init__(self, title, date, time, location, description):
15 self.title = title
16 self.date = date
17 self.time = time
18 self.location = location
19 self.description = description
20
21 def __repr__(self):
22 return f"<Event {self.title}>"

This describes the table that we want to add to the database. It will be the events table and it'll have six columns to hold details about every event we add. This is all we need to run the database migration. To do that, go to the server directory in your terminal and run the following.

1$ flask db init

This will add a migrations folder to your project. Next, we need to generate the migration for our model with the following command.

1$ flask db migrate -m "added events table"

Finally, we can run this migration and make these changes in the database with the following command.

1$ flask db upgrade

If you take a look in your Postgres instance, you'll see the new table and all of the fields we defined in the model. Now we can start creating the endpoints that will work with the data we store in this table.

Create an event

Let's start with the endpoint that will allow users to create new events on the calendar. Below the events model, add the following code.

1# api.py
2
3...
4@app.route("/event", methods=["POST"])
5def create_event():
6 if request.is_json:
7 data = request.get_json()
8
9 new_event = EventsModel(
10 title=data["title"],
11 date=data["date"],
12 time=data["time"],
13 location=data["location"],
14 description=data["description"],
15 )
16
17 db.session.add(new_event)
18 db.session.commit()
19
20 return {"message": f"event {new_event.title} has been created successfully."}
21 else:
22 return {"error": "The request payload is not in JSON format"}

When a POST request is made to the /event endpoint with the correct data, it will add a new record to the events table in the database and send a success message with the event title in the response.

Get all of the events

We'll need to be able to get all of the events that are in the calendar so that we can render them correctly. To fetch all of the events from the database, add the following code to your api.py file below the create event endpoint.

1# api.py
2
3...
4@app.route("/events", methods=["GET"])
5def get_events():
6 eventsData = EventsModel.query.all()
7 events = [
8 {
9 "id": event.id,
10 "title": event.title,
11 "date": event.date,
12 "time": event.time,
13 "location": event.location,
14 "description": event.description,
15 }
16 for event in eventsData
17 ]
18
19 return {"events": json.dumps(events, default=str)}

This creates a new GET route called /events. Anytime this endpoint is called with a GET request, it will go to the database and retrieve all of the records from the events table and return it as a JSON string.

Get and update an event

We can take advantage of the request type to use the same endpoint to handle different functionality. There will be times we want to look at or edit a particular event. We could use separate endpoints for this, but we're going to use different request types to distinguish which type of call is being made. Add the following code below the endpoint to get all of the events.

1# api.py
2
3...
4@app.route("/event/<event_id>", methods=["GET", "PUT"])
5def event(event_id):
6 event = EventsModel.query.get_or_404(event_id)
7
8 if request.method == "GET":
9 response = {
10 "id": event.id,
11 "title": event.title,
12 "date": event.date,
13 "time": event.time,
14 "location": event.location,
15 "description": event.description,
16 }
17
18 return {"message": "success", "event": json.dumps(response, default=str)}
19 else:
20 data = request.get_json()
21
22 event.title = data["title"]
23 event.date = data["date"]
24 event.time = data["time"]
25 event.location = data["location"]
26 event.description = data["description"]
27
28 db.session.add(event)
29 db.session.commit()
30
31 return {"message": f"event {event.title} successfully updated"}

This checks that the database has a record for the given event ID or it returns an error. If there is a record, the request type is checked and if it is a GET request, it will return a JSON string with that event's details. If it's a POST request, it will update the event record and save the changes to the database.

Run the file correctly

We're going to run this as the main program so we'll add this bit of code at the bottom of our file.

1# api.py
2
3...
4if __name__ == "__main__":
5 app.run()

You can test this out by running the app with the following command in your terminal.

1$ python api.py

You should see your server start on localhost:5000 and try sending a request to the /event endpoint to make a new record. This is actually everything we need for the API! We have the database connection and all of the endpoints we need to work with data on the front-end so we can switch our attention there.

Building the React App

Switching over to the front-end, go to the root directory in the terminal and run the following command.

1$ npx create-react-app client --template typescript

This will generate a new React project for you in a new directory called client. In your terminal, go to the client directory and run the following command to add the packages we'll use.

1$ npm i axios react-calendar react-modal

With the packages installed, let's start by making the one component we need for this project.

Add the event modal

In the client folder, add a new subfolder called components. Inside that folder, add a new file called Event.tsx. This will be the modal that appears when users want to view or edit an individual event. Add the following code to that file.

1// Event.tsx
2import axios from "axios";
3import Modal from "react-modal";
4
5Modal.setAppElement("#root");
6
7export default function Event({
8 showModal,
9 setShowModal,
10 event,
11}: {
12 showModal: boolean;
13 setShowModal: () => void;
14 event?: any;
15}) {
16 const saveEvent = async (event: any) => {
17 event.preventDefault();
18 const eventData = {
19 title: event.target.title.value,
20 date: event.target.date.value,
21 time: event.target.time.value,
22 location: event.target.location.value,
23 description: event.target.description.value,
24 };
25
26 if (!event) {
27 await axios.post("http://localhost:5000/event", eventData);
28 } else {
29 await axios.put(`http://localhost:5000/event/${event.id}`, eventData);
30 }
31 };
32
33 return (
34 <>
35 <Modal
36 isOpen={showModal}
37 onRequestClose={setShowModal}
38 contentLabel="Event"
39 >
40 <div>Event</div>
41 <form onSubmit={saveEvent}>
42 <div>
43 <label htmlFor="title">Event title</label>
44 <input
45 name="title"
46 type="text"
47 id="title"
48 defaultValue={event?.title}
49 />
50 </div>
51 <div>
52 <label htmlFor="date">Event dateime</label>
53 <input
54 name="date"
55 type="text"
56 id="date"
57 defaultValue={event?.date}
58 />
59 </div>
60 <div>
61 <label htmlFor="time">Event time</label>
62 <input
63 name="time"
64 type="text"
65 id="time"
66 defaultValue={event?.time}
67 />
68 </div>
69 <div>
70 <label htmlFor="location">Event location</label>
71 <input
72 name="location"
73 type="text"
74 id="location"
75 defaultValue={event?.location}
76 />
77 </div>
78 <div>
79 <label htmlFor="description">Event title</label>
80 <input
81 name="description"
82 type="text"
83 id="description"
84 defaultValue={event?.description}
85 />
86 </div>
87 <button onClick={setShowModal}>Close modal</button>
88 <button type="submit">Save event</button>
89 </form>
90 </Modal>
91 </>
92 );
93}

This imports a few packages and sets up the modal element to target the root element. Then we have the Event component which takes a few props. Then there is a saveEvent method that will take the values from the form we have and either update or create a new event based on if there is any current event data available.

Then we have the form that will either be blank or display the event information in the fields depending on if the event prop was passed to the modal. We also decide whether the modal should currently be showing or if it should be hidden based on the showModal prop.

That's all for the modal to show, update, or create new events. Now we need the page that shows the calendar and all of the events we have.

The calendar page

We're going to modify the existing App.tsx file to render our calendar and the events. Here's what the new code will look like. There's a lot going on here so we'll walk through it.

1// App.tsx
2
3import axios from "axios";
4import { useEffect, useState } from "react";
5import Calendar from "react-calendar";
6import "react-calendar/dist/Calendar.css";
7import Event from "./components/Event";
8
9export default function App() {
10 const [date, setDate] = useState(new Date());
11 const [showModal, setShowModal] = useState<boolean>(false);
12 const [showEditModal, setShowEditModal] = useState<boolean>(false);
13 const [event, setEvent] = useState({});
14 const [events, setEvents] = useState<any>([]);
15
16 useEffect(() => {
17 axios.get("http://localhost:5000/events").then((res) => {
18 setEvents(JSON.parse(res.data.events));
19 });
20 }, []);
21
22 function tileContent({ date, view }: any) {
23 const display = events.map((event: any) => {
24 const eventDate = new Date(event.date);
25 eventDate.setDate(eventDate.getDate() + 1);
26
27 if (date.toDateString() === eventDate.toDateString()) {
28 return " " + event.title + " ";
29 }
30 return "";
31 });
32 return display;
33 }
34
35 return (
36 <div>
37 <h1>Event Calendar</h1>
38 <Calendar
39 onChange={setDate}
40 value={date}
41 onClickDay={() => setShowModal(true)}
42 tileContent={tileContent}
43 />
44 <>
45 {events &&
46 events.map((event: any) => (
47 <div
48 key={event.title}
49 onClick={() => {
50 setEvent(event);
51 setShowEditModal(true);
52 }}
53 style={{ borderBottom: "1px solid grey", padding: "18px" }}
54 >
55 <div>{event.title}</div>
56 <div>
57 {event.date} - {event.time}
58 </div>
59 <div>{event.location}</div>
60 <div>{event.description}</div>
61 </div>
62 ))}
63 </>
64 {showEditModal && (
65 <Event
66 showModal={showEditModal}
67 setShowModal={() => setShowEditModal(false)}
68 event={event}
69 />
70 )}
71 {showModal && (
72 <Event showModal={showModal} setShowModal={() => setShowModal(false)} />
73 )}
74 </div>
75 );
76}

We start off by importing the packages we need to get the calendar and the events data. Inside the App component we have some states that will control how elements are rendered on the page. After that, we have a useEffect hook that fetches all of the events from the database when the page is loaded.

Then there's a function that adds the title of an event to the date on the calendar. Next, we render the Calendar component and pass it a few props. After that, we display a list of all of the events that are currently on the calendar. Finally, we conditionally show the event modal based on current states.

The last thing we need to edit is the CSS so this looks a little better.

Style the calendar

There will be a lot of things you need to change stylistically before this goes to production, but we'll at least add a few things to get this to an MVP stage. Find the index.scss file in client > src and add the following code at the bottom.

1// index.scss
2
3... .react-calendar {
4 width: 100% !important;
5 border: solid 1px white;
6 border-radius: 12px;
7}

This will at least make the calendar the full width of the page so people can see the event titles. Here's what the calendar page will look like if you run the front-end and back-end at the same time.

Now you have a full-stack application ready to handle all of the events in your community!

Finished code

You can find the code for the finished project in the events-calendar folder of this repo. You can also check out this Code Sandbox for the front-end.

Conclusion

Planning events is hard enough without trying to figure out how to spread the word. This might be a way to encourage people to let others know what is going on and how they can participate. This project should be flexible enough that you can style it and expand the functionality in a number of ways.

Milecia

Software Team Lead

Milecia is a senior software engineer, international tech speaker, and mad scientist that works with hardware and software. She will try to make anything with JavaScript first.