DEV Community

Cover image for Build a fullstack twitter clone using Flask and React | PART 1
arnu515
arnu515

Posted on • Updated on

Build a fullstack twitter clone using Flask and React | PART 1

Hello, community! It's my first post here, and in this post, I want to show you how you can make a simple fullstack twitter clone using React and Flask. This will be a 3 part series and in this part, we will build the application.

Before we start, I assume you know flask, react, javascript, python and html


Project setup

I will be using VSCode as my IDE, but you can use anything you like (Pycharm, Webstorm, Atom are some examples). I'll be showing instructions for my IDE.

Creating our project

Create a folder which will house your project and open a terminal window there. I like to do things using the UNIX terminal (BASH SHELL), but, you can use a GUI based file explorer too. Let's create the backend folder.

mkdir backend
Enter fullscreen mode Exit fullscreen mode

I'll use create-react-app to create the frontend folder with a react app initialised.

npx create-react-app frontend
Enter fullscreen mode Exit fullscreen mode

Now, you should have 2 folders, frontend and backend.

$ ls
backend
frontend
Enter fullscreen mode Exit fullscreen mode

Nice! Now, let's set up the backend. cd into the backend folder and create a virtual environment for flask.
You can use pipenv or not use a virtual environment at all!

python3 -m venv venv && . venv/bin/activate
Enter fullscreen mode Exit fullscreen mode

That should create a virtual environment named venv and activate it. Now, cd into our main project folder and open it using VSCode by typing code ..


Creating a basic Flask project

Now, let's set up the backend. First, we need to install flask.

pip install flask
Enter fullscreen mode Exit fullscreen mode

Make sure your virtualenv is activated

Now, create a file named app.py. This will be the main file for our backend.

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return "Hello, world!"

if __name__ == "__main__":
    app.run(debug=True) # debug=True restarts the server everytime we make a change in our code
Enter fullscreen mode Exit fullscreen mode

Now, let's run the backend by typing

python3 app.py
Enter fullscreen mode Exit fullscreen mode

Let's test if the server works. You can visit localhost:5000 in your browser or use a REST Client like Insomnia if you want. For testing post methods, I'll be using cURL, which is a command-line utility, but you can use a GUI REST Client.

For a database, we can use something called Flask-SQLAlchemy, which is SQLAlchemy or sqlite3 for Flask. Let's first shutdown our server by pressing CTRL + C to terminate it and let's install flask-sqlalchemy.

pip install flask-sqlalchemy
Enter fullscreen mode Exit fullscreen mode

Now, back in app.py, let's import flask-sqlalchemy.

from flask_sqlalchemy import SQLAlchemy
Enter fullscreen mode Exit fullscreen mode

And under where we defined app, write this code:

app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///twitter.db"

# DB
db = SQLAlchemy(app)
class Users(db.Model):
    id = db.Column('student_id', db.Integer, primary_key = True) # primary_key makes it so that this value is unique and can be used to identify this record.
    username = db.Column(db.String(24))
    email = db.Column(db.String(64))
    pwd = db.Column(db.String(64))

    # Constructor
    def __init__(self, username, email, pwd):
        self.username = username
        self.email = email
        self.pwd = pwd
Enter fullscreen mode Exit fullscreen mode

So what this code does, is first, it sets our database file as twitter.db. You can change this name if you want. Then, the users class you see is a Model, which lays out what your users table's columns are. Pretty standard SQL Stuff.

Now, temporarily, above the return statement in the index function write this:

db.create_all()
Enter fullscreen mode Exit fullscreen mode

Your file should look like this:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///twitter.db"

# DB
db = SQLAlchemy(app)
class Users(db.Model):
    id = db.Column('student_id', db.Integer, primary_key = True) # primary_key makes it so that this value is unique and can be used to identify this record.
    username = db.Column(db.String(24))
    email = db.Column(db.String(64))
    pwd = db.Column(db.String(64))

    # Constructor
    def __init__(self, username, email, pwd):
        self.username = username
        self.email = email
        self.pwd = pwd

@app.route('/')
def index():
    return "Hello, world!"

if __name__ == "__main__":
    app.run(debug=True)
Enter fullscreen mode Exit fullscreen mode

Start the server and visit localhost:5000 or perform a GET request using your favorite REST client. For example, in cURL, you'd type

curl "http://localhost:5000"
Enter fullscreen mode Exit fullscreen mode

And you should get the same response back, but now, when you look in your folder, you can find a new file called twitter.db created. This is your database. Unlike something like MYSQL, MariaDB or MongoDB, sqlite does not need a server to function.


Basic CRUD routes

Now, let's perform some basic CRUD (Create Read Update Destroy) actions by adding some more routes to our app. First, delete the index route entirely. We won't be needing it. I'm gonna prefix all of my routes with /api.

@app.route("/api/users", methods=["GET", "POST", "DELETE"])
def users():
    method = request.method
    if (method.lower() == "get"): # READ
        users = Users.query.all()
        return jsonify([{"id": i.id, "username": i.username, "email": i.email, "password": i.pwd} for i in users]) # Get all values from db
    elif (method.lower() == "post"): # CREATE
        try:
            username = request.json["username"]
            email = request.json["email"]
            pwd = request.json["pwd"]
            if (username and pwd and email): # Checks if username, pwd or email are empty
                try:
                    user = Users(username, email, pwd) # Creates a new record
                    db.session.add(user) # Adds the record for committing
                    db.session.commit() # Saves our changes
                    return jsonify({"success": True})
                except Exception as e:
                    return ({"error": e})
            else:
                return jsonify({"error": "Invalid form"}) # jsonify converts python vars to json
        except:
            return jsonify({"error": "Invalid form"})
    elif (method.lower() == "delete"): # DESTROY
        try:
            uid = request.json["id"]
            if (uid):
                try:
                    user = Users.query.get(uid) # Gets user with id = uid (because id is primary key)
                    db.session.delete(user) # Delete the user
                    db.session.commit() # Save
                    return jsonify({"success": True})
                except Exception as e:
                    return jsonify({"error": e})
            else:
                return jsonify({"error": "Invalid form"})
        except:
            return jsonify({"error": "m"})
Enter fullscreen mode Exit fullscreen mode
  • We'll work on the PUT method a bit later * Now, we have a route in our app called /api/users. We can perform Create, Read and Destroy actions through different HTTP methods. More about that here

Now, this is what our code should look like:

from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy

from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///twitter.db"

# DB
db = SQLAlchemy(app)
class Users(db.Model):
    id = db.Column('student_id', db.Integer, primary_key = True) # primary_key makes it so that this value is unique and can be used to identify this record.
    username = db.Column(db.String(24))
    email = db.Column(db.String(64))
    pwd = db.Column(db.String(64))

    # Constructor
    def __init__(self, username, email, pwd):
        self.username = username
        self.email = email
        self.pwd = pwd

# Routes

# Users
@app.route("/api/users", methods=["GET", "POST", "DELETE"])
def users():
    method = request.method
    if (method.lower() == "get"): # READ
        users = Users.query.all()
        return jsonify([{"id": i.id, "username": i.username, "email": i.email, "password": i.pwd} for i in users]) # Get all values from db
    elif (method.lower() == "post"): # CREATE
        try:
            username = request.json["username"]
            email = request.json["email"]
            pwd = request.json["pwd"]
            if (username and pwd and email): # Checks if username, pwd or email are empty
                try:
                    user = Users(username, email, pwd) # Creates a new record
                    db.session.add(user) # Adds the record for committing
                    db.session.commit() # Saves our changes
                    return jsonify({"success": True})
                except Exception as e:
                    return ({"error": e})
            else:
                return jsonify({"error": "Invalid form"}) # jsonify converts python vars to json
        except:
            return jsonify({"error": "Invalid form"})
    elif (method.lower() == "delete"): # DESTROY
        try:
            uid = request.json["id"]
            if (uid):
                try:
                    user = Users.query.get(uid) # Gets user with id = uid (because id is primary key)
                    db.session.delete(user) # Delete the user
                    db.session.commit() # Save
                    return jsonify({"success": True})
                except Exception as e:
                    return jsonify({"error": e})
            else:
                return jsonify({"error": "Invalid form"})
        except:
            return jsonify({"error": "m"})
                return ({"error": "Invalid form"})
        except:
            return ({"error": "Invalid form"})

if __name__ == "__main__":
    app.run(debug=True)
Enter fullscreen mode Exit fullscreen mode

Let's give this man a test drive!
First, lets perform a GET request to /api/users.

curl "http://localhost:5000/api/users"
# OUTPUT: []
Enter fullscreen mode Exit fullscreen mode

We get an empty array because the database has no data

Now, let's give it some data. Perform a POST request to /api/users with some data.

curl -X POST -H "Content-Type: application/json" -d '{"username": "foo", "email": "foo@bar.com", "pwd": "bar"}' "http://localhost:5000/api/users"
Enter fullscreen mode Exit fullscreen mode

If you GET the data again, you can notice a record has been made. Now, let's delete a user

curl -X DELETE -H "Content-Type: application/json" -d '{"id": 1}' "http://localhost:5000/api/users"
Enter fullscreen mode Exit fullscreen mode

And if we query the data, we should get back an empty string! We'll do PUT later.

And that should be it (for now) for our flask application. Go ahead and stop the server(^C)


Creating the frontend

Now, let's focus on the frontend. Open the frontend folder (or whatever you called it) in the terminal and type:

npm start
Enter fullscreen mode Exit fullscreen mode

This should open a webpage at localhost:3000 and you should see this:
localhost:3000

Creating the homepage

The default template create-react-app gives us is not what we need, so, let's delete the content of our public and src folder. You will see your app crash, but that's fine. Also, create public/index.html, src/index.js, src/components, src/components/App.jsx, src/components/Home.jsx, src/components/Navbar.jsx

rm -r public/* src/*  
mkdir src/components  
touch public/index.html src/index.js src/components/App.jsx src/components/Home.jsx src/components/Navbar.jsx
Enter fullscreen mode Exit fullscreen mode

Now, let's set the code for index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Quickr</title>
        <!-- W3CSS -->
        <link rel="stylesheet" href="https://w3schools.com/w3css/4/w3.css" />
    </head>
    <body>
        <div id="root"></div>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

I am using W3CSS for my css styling as it is quick and easy to use

Now, for all the javascript files:

// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";

ReactDOM.render(<App />, document.getElementById("root"));
Enter fullscreen mode Exit fullscreen mode
// src/components/App.jsx
import React from "react";
import Home from "./Home";
import Navbar from "./Navbar";

function App() {
    return (
        <React.Fragment>
            <Navbar />
            <Home />
        </React.Fragment>
    );
}

export default App;

Enter fullscreen mode Exit fullscreen mode
// src/components/Home.jsx
import React from "react";

function Home() {
    return (
        <React.Fragment>
            <div
                className="w3-container w3-center w3-blue"
                style={{ padding: "2rem" }}>
                <h1 className="w3-jumbo">Quickr - Quick Twitter</h1>
                <button
                    className="w3-button w3-pink"
                    style={{ marginRight: "1rem" }}>
                    Login
                </button>
                <button className="w3-button w3-pink">Register</button>
            </div>

            <div
                className="w3-container w3-blue"
                style={{ padding: "2rem", marginTop: "2rem" }}>
                <h2>Lorem ipsum dolor sit amet</h2>
                <p>
                    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
                    Integer consectetur libero nibh, non sodales urna malesuada
                    nec. Sed tortor eros, blandit eget fringilla at, gravida a
                    nibh. Etiam dui nulla, aliquam vitae purus a, auctor
                    malesuada arcu. Vestibulum venenatis orci nisl, sed
                    elementum leo tincidunt eget. Nullam convallis nisi in.
                </p>
            </div>

            <div
                className="w3-container w3-blue"
                style={{ padding: "2rem", marginTop: "2rem" }}>
                <h2>Lorem ipsum dolor sit amet</h2>
                <p>
                    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
                    Integer consectetur libero nibh, non sodales urna malesuada
                    nec. Sed tortor eros, blandit eget fringilla at, gravida a
                    nibh. Etiam dui nulla, aliquam vitae purus a, auctor
                    malesuada arcu. Vestibulum venenatis orci nisl, sed
                    elementum leo tincidunt eget. Nullam convallis nisi in.
                </p>
            </div>

            <div
                className="w3-container w3-blue"
                style={{ padding: "2rem", marginTop: "2rem" }}>
                <h2>Lorem ipsum dolor sit amet</h2>
                <p>
                    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
                    Integer consectetur libero nibh, non sodales urna malesuada
                    nec. Sed tortor eros, blandit eget fringilla at, gravida a
                    nibh. Etiam dui nulla, aliquam vitae purus a, auctor
                    malesuada arcu. Vestibulum venenatis orci nisl, sed
                    elementum leo tincidunt eget. Nullam convallis nisi in.
                </p>
            </div>
        </React.Fragment>
    );
}

export default Home;
Enter fullscreen mode Exit fullscreen mode
// src/components/Navbar.jsx
import React from "react";

function Navbar() {
    return (
        <div className="w3-bar w3-black">
            <a className="w3-bar-item w3-button" href="/">
                Quickr
            </a>
            <div style={{ float: "right" }}>
                <a className="w3-bar-item w3-button" href="/">
                    Login
                </a>
                <a className="w3-bar-item w3-button" href="/">
                    Register
                </a>
            </div>
        </div>
    );
}

export default Navbar;
Enter fullscreen mode Exit fullscreen mode

And finally, your website should look like this:
Website

Now, let's add some functionality!


Connecting backend to frontend

Now, let's implement login and register. Remember the routes we made earlier? Let's convert them to functions

def getUsers():
    users = Users.query.all()
    return [{"id": i.id, "username": i.username, "email": i.email, "password": i.pwd} for i in users]

def addUser(username, email, pwd):
    if (username and pwd and email):
        try:
            user = Users(username, email, pwd)
            db.session.add(user)
            db.session.commit()
            return True
        except Exception as e:
            print(e)
            return False
    else:
        return False

def removeUser(uid):
    uid = request.json["id"]
    if (uid):
        try:
            user = Users.query.get(uid)
            db.session.delete(user)
            db.session.commit()
            return True
        except Exception as e:
            print(e)
            return False
    else:
        return False
Enter fullscreen mode Exit fullscreen mode

Now, let's add a login and register api route. I'm gonna prefix my routes with /api like before.

@app.route("/api/login", methods=["POST"])
def login():
    try:
        email = request.json["email"]
        password = request.json["pwd"]
        if (email and password):
            users = getUsers()
            # Check if user exists
            return jsonify(len(list(filter(lambda x: x["email"] == email and x["password"] == password, users))) == 1)
        else:
            return jsonify({"error": "Invalid form"})
    except:
        return jsonify({"error": "Invalid form"})

@app.route("/api/register", methods=["POST"])
def register():
    try:
        email = request.json["email"]
        email = email.lower()
        password = request.json["pwd"]
        username = request.json["username"]
        # Check to see if user already exists
        users = getUsers()
        if(len(list(filter(lambda x: x["email"] == email, users))) == 1):
            return jsonify({"error": "Invalid form"})
        # Email validation check
        if not re.match(r"[\w\._]{5,}@\w{3,}.\w{2,4}", email):
            return jsonify({"error": "Invalid form"})
        addUser(username, email, password)
        return jsonify({"success": True})
    except:
        return jsonify({"error": "Invalid form"})
Enter fullscreen mode Exit fullscreen mode

In our register method, we have some form validation. It checks if the email is already registered and if the email is a valid email. Now, let's test it out. Start your server and make some requests.

# REGISTER
curl -X POST -H "Content-Type: application/json" -d '{"email": "foo@bar.dev", "pwd": "foobar", "username": "foobar"}' "http://localhost:5000/api/register" 
Enter fullscreen mode Exit fullscreen mode
# LOGIN
curl -X POST -H "Content-Type: application/json" -d '{"email": "azeez@man.com", "pwd": "azeez"}' "http://localhost:5000/api/login"
Enter fullscreen mode Exit fullscreen mode

So, now, we can login to our database using the CLI. But, we need to allow users to login using the Website. Let's head over to the frontend.


So, I have made 2 files, Login.jsx and Register.jsx. These files contain a login and register form separately. You can use your own markup, but for anyone wanting to use mine, here you go:

// src/components/Login.jsx
import React, { Component } from "react";

class Login extends Component {
    render() {
        return (
            <div className="w3-card-4" style={{ margin: "2rem" }}>
                <div className="w3-container w3-blue w3-center w3-xlarge">
                    LOGIN
                </div>
                <div className="w3-container">
                    <form>
                        <p>
                            <label htmlFor="email">Email</label>
                            <input
                                type="email"
                                class="w3-input w3-border"
                                id="email"
                            />
                        </p>
                        <p>
                            <label htmlFor="password">Password</label>
                            <input
                                type="password"
                                class="w3-input w3-border"
                                id="password"
                            />
                        </p>
                        <p>
                            <button type="submit" class="w3-button w3-blue">
                                Login
                            </button>
                        </p>
                    </form>
                </div>
            </div>
        );
    }
}

export default Login;
Enter fullscreen mode Exit fullscreen mode
// src/components/Register.jsx
import React, { Component } from "react";

class Register extends Component {
    render() {
        return (
            <div className="w3-card-4" style={{ margin: "2rem" }}>
                <div className="w3-container w3-blue w3-center w3-xlarge">
                    REGISTER
                </div>
                <div className="w3-container">
                    <form>
                        <p>
                            <label htmlFor="email">Email</label>
                            <input
                                type="email"
                                class="w3-input w3-border"
                                id="email"
                            />
                        </p>
                        <p>
                            <label htmlFor="username">Username</label>
                            <input
                                type="username"
                                class="w3-input w3-border"
                                id="text"
                            />
                        </p>
                        <p>
                            <label htmlFor="password">Password</label>
                            <input
                                type="password"
                                class="w3-input w3-border"
                                id="password"
                            />
                        </p>
                        <p>
                            <button type="submit" class="w3-button w3-blue">
                                Register
                            </button>
                        </p>
                    </form>
                </div>
            </div>
        );
    }
}

export default Register;
Enter fullscreen mode Exit fullscreen mode

Ok, we're done with our markup, but, how can we render it in a different route? For example, if I type https://mywebsite.com/login, I need to see the login page. This can be done by using react-router-dom. First, install react-router-dom

npm i react-router-dom
Enter fullscreen mode Exit fullscreen mode

And, now, let's implement it in our App.jsx

// src/components/App.jsx
import React from "react";
import Home from "./Home";
import Navbar from "./Navbar";
import Login from "./Login";
import Register from "./Register";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";

function App() {
    return (
        <React.Fragment>
            <Navbar />
            <Router>
                <Route path="/" exact component={Home} />
                <Route path="/login" exact component={Login} />
                <Route path="/register" exact component={Register} />
            </Router>
        </React.Fragment>
    );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

I also updated the routes in Navbar.jsx.

// src/components/Navbar.jsx
import React from "react";

function Navbar() {
    return (
        <div className="w3-bar w3-black">
            <a className="w3-bar-item w3-button" href="/">
                Quickr
            </a>
            <div style={{ float: "right" }}>
                <a className="w3-bar-item w3-button" href="/login"> // Updated
                    Login
                </a>
                <a className="w3-bar-item w3-button" href="/register"> // Updated
                    Register
                </a>
            </div>
        </div>
    );
}

export default Navbar;
Enter fullscreen mode Exit fullscreen mode

Now, when you click on the login and register buttons in the navbar, you should see the components we created. Now, let's add some functionality to the components.

Login

First, let's install something called axios.

npm i axios
Enter fullscreen mode Exit fullscreen mode

Now, let's add a function that sends a POST request to the backend.

// Login.jsx
import React, { Component } from "react";
import axios from "axios";

class Login extends Component {
    login = (e) => {
        e.preventDefault();
        axios
            .post("http://localhost:5000/api/login", {
                email: document.getElementById("email").value,
                pwd: document.getElementById("password").value,
            })
            .then((res) => {
                console.log(res.data);
            });
    };

    render() {
        return (
            <div className="w3-card-4" style={{ margin: "2rem" }}>
                <div className="w3-container w3-blue w3-center w3-xlarge">
                    LOGIN
                </div>
                <div className="w3-container">
                    <form onSubmit={this.login}>
                        <p>
                            <label htmlFor="email">Email</label>
                            <input
                                type="email"
                                className="w3-input w3-border"
                                id="email"
                            />
                        </p>
                        <p>
                            <label htmlFor="password">Password</label>
                            <input
                                type="password"
                                className="w3-input w3-border"
                                id="password"
                            />
                        </p>
                        <p>
                            <button type="submit" class="w3-button w3-blue">
                                Login
                            </button>
                        </p>
                    </form>
                </div>
            </div>
        );
    }
}

export default Login;
Enter fullscreen mode Exit fullscreen mode
import React, { Component } from "react";
import axios from "axios";

class Register extends Component {
    register = (e) => {
        e.preventDefault();
        axios
            .post("http://localhost:5000/api/register", {
                email: document.getElementById("email").value,
                username: document.getElementById("username").value,
                pwd: document.getElementById("password").value,
            })
            .then((res) => {
                console.log(res.data);
            });
    };

    render() {
        return (
            <div className="w3-card-4" style={{ margin: "2rem" }}>
                <div className="w3-container w3-blue w3-center w3-xlarge">
                    REGISTER
                </div>
                <div className="w3-container">
                    <form onSubmit={this.register}>
                        <p>
                            <label htmlFor="email">Email</label>
                            <input
                                type="email"
                                className="w3-input w3-border"
                                id="email"
                            />
                        </p>
                        <p>
                            <label htmlFor="username">Username</label>
                            <input
                                type="text"
                                className="w3-input w3-border"
                                id="username"
                            />
                        </p>
                        <p>
                            <label htmlFor="password">Password</label>
                            <input
                                type="password"
                                className="w3-input w3-border"
                                id="password"
                            />
                        </p>
                        <p>
                            <button type="submit" class="w3-button w3-blue">
                                Register
                            </button>
                        </p>
                    </form>
                </div>
            </div>
        );
    }
}

export default Register;
Enter fullscreen mode Exit fullscreen mode

So what we're doing here, is whenever the form is submitted, we send a request to the backend to the login and register routes. And when we get a response, we log it. Now, enter some rubbish data in Register, and open the in-browser console (or devtools) by pressing Ctrl+Shift+I for Windows/Linux or Cmd+Option+I for Mac. Press the register button and in the console, you should see an error like this:
Error

Now, this error is a security measure put in place by something called CORS. To bypass this, we need to install something called flask-cors in our backend.

pip install flask-cors
Enter fullscreen mode Exit fullscreen mode

Now, change the starting of your app.py to this:

from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
import re

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///twitter.db"
CORS(app)
# ...
Enter fullscreen mode Exit fullscreen mode

If you now click register, you should see {success: true} in the console. And now, if we login using the same email and password, you can see true in the console, meaning everything is working! Now, let's give the user some feedback. Create a new file called Alert.jsx and put this in it:

import React from "react";

function Alert(props) {
    return (
        <div
            className="w3-pale-red w3-text-red w3-border w3-border-red w3-round-large"
            style={{ padding: "1rem", marginTop: "1rem" }}>
            {props.message}
        </div>
    );
}

export default Alert;
Enter fullscreen mode Exit fullscreen mode

Alert is now a reusable component that we can use anywhere in our code and we can give it a message. It'll display that message in a red background and in red text. We can make use of this component in Register.jsx:

import React, { Component } from "react";
import axios from "axios";
import Alert from "./Alert";

class Register extends Component {
    state = { err: "" };

    register = (e) => {
        e.preventDefault();
        axios
            .post("http://localhost:5000/api/register", {
                email: document.getElementById("email").value,
                username: document.getElementById("username").value,
                pwd: document.getElementById("password").value,
            })
            .then((res) => {
                if (res.data.error) {
                    this.setState({ err: res.data.error });
                } else {
                    this.setState({ register: true });
                }
            });
    };

    render() {
        return (
            <div className="w3-card-4" style={{ margin: "2rem" }}>
                <div className="w3-container w3-blue w3-center w3-xlarge">
                    REGISTER
                </div>
                <div className="w3-container">
                    {this.state.err.length > 0 && (
                        <Alert
                            message={`Check your form and try again! (${this.state.err})`}
                        />
                    )}
                    <form onSubmit={this.register}>
                        <p>
                            <label htmlFor="email">Email</label>
                            <input
                                type="email"
                                class="w3-input w3-border"
                                id="email"
                            />
                        </p>
                        <p>
                            <label htmlFor="username">Username</label>
                            <input
                                type="text"
                                class="w3-input w3-border"
                                id="username"
                            />
                        </p>
                        <p>
                            <label htmlFor="password">Password</label>
                            <input
                                type="password"
                                class="w3-input w3-border"
                                id="password"
                            />
                        </p>
                        <p>
                            <button type="submit" class="w3-button w3-blue">
                                Register
                            </button>
                            {this.state.register && <p>You're registered!</p>}
                        </p>
                    </form>
                </div>
            </div>
        );
    }
}

export default Register;
Enter fullscreen mode Exit fullscreen mode

Now, if we enter an invalid email, (a valid email, according to my RegEx, is one that has 5 or more alphanumberic characters including . and _, an @, 3 or more alphanumeric characters, a . and 2-4 more alphanumberic characters), we should get this:
Register

Awesome! Login.jsx can also use some error handling.

import React, { Component } from "react";
import axios from "axios";
import Alert from "./Alert";

class Login extends Component {
    state = { err: "" };

    login = (e) => {
        e.preventDefault();
        axios
            .post("http://localhost:5000/api/login", {
                email: document.getElementById("email").value,
                pwd: document.getElementById("password").value,
            })
            .then((res) => {
                if (res.data.error) {
                    this.setState({ err: res.data.error });
                } else {
                    this.setState({ login: true });
                }
            });
    };

    render() {
        return (
            <div className="w3-card-4" style={{ margin: "2rem" }}>
                <div className="w3-container w3-blue w3-center w3-xlarge">
                    LOGIN
                </div>
                <div className="w3-container">
                    {this.state.err.length > 0 && (
                        <Alert
                            message={`Check your form and try again! (${this.state.err})`}
                        />
                    )}
                    <form onSubmit={this.login}>
                        <p>
                            <label htmlFor="email">Email</label>
                            <input
                                type="email"
                                class="w3-input w3-border"
                                id="email"
                            />
                        </p>
                        <p>
                            <label htmlFor="password">Password</label>
                            <input
                                type="password"
                                class="w3-input w3-border"
                                id="password"
                            />
                        </p>
                        <p>
                            <button type="submit" class="w3-button w3-blue">
                                Login
                            </button>
                            {this.state.register && <p>You're logged in!</p>}
                        </p>
                    </form>
                </div>
            </div>
        );
    }
}

export default Login;
Enter fullscreen mode Exit fullscreen mode

And now, if we type wrong credentials, we should get this:
Login


Conclusion

Nice! Now, you've learnt how to connect flask and react together using a RESTful API. In the next part, we'll add the ability to post and see tweets. This post has been pretty long. I'll try to make a youtube video about it, and if I do, I'll be sure to link it here. All the code is available on Github. This was pretty long for a first post!

Part Two is now out!

Oldest comments (12)

Collapse
 
fatheyabdelslam profile image
Fathey

nice Article,
i can't wait part two

Collapse
 
arnu515 profile image
arnu515 • Edited

Thanks! I'm working on it right now.

UPDATE: It is out dev.to/arnu515/build-a-twitter-clo...

Collapse
 
arnu515 profile image
arnu515

Part two is now out! Check it out here

Collapse
 
kgprajwal profile image
Prajwal

Very nice!

Collapse
 
arnu515 profile image
arnu515

Thanks!

Collapse
 
otto_livingston profile image
No 680621498

Thanks for this piece

Collapse
 
arnu515 profile image
arnu515

Your welcome!

Collapse
 
otto_livingston profile image
No 680621498 • Edited

I keep receiving
{
"error": "Invalid Form"
}

After running this to the terminal

curl -X POST -H "Content-Type: application/json" -d '{"email": "foo@bar.dev", "pwd": "foobar", "username": "foobar"}' "localhost:5000/api/register"

I need help here

Collapse
 
arnu515 profile image
arnu515

Hmm, sorry for the late reply, but I think that email is already registered. Try changing each of the errors to something meaningful, so that you can understand what's going on

Collapse
 
emilie profile image
Emilie

hey you mustn't manipulate DOM directly on react :-)
But considering you are only 14 years old. I am so impressed by your skillsets!

Collapse
 
arnu515 profile image
arnu515

Thanks! I will keep it in mind!

Also I'm 15 now :)

Collapse
 
benayat profile image
benayat

great article, thanks!
one note - with axios, there is no need to insert the entire url with localhost - just the endpoint would be enough. this way it can stay the same for dev and deployment.
just add a proxy to package.json