Install nextjs npx create-next-app@latest nextjs_org/docs/getting-started/installation
Install react-query
npm i @tanstack/react-query
tanstack com/query/latest/docs/framework/react/installation
app\page.tsx
//app\page.tsx "use client"; import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query https://tanstack.com/query/latest/docs/framework/react/installation import CustomerList from "./components/CustomerList"; import { getCustomerList } from "./api/customer"; import Paginationnumber from "./components/Paginationnumber"; import LoadingSpinner from "./components/LoadingSpinner"; import { useSearchParams } from 'next/navigation' import Link from "next/link"; export default function Home() { const searchParams = useSearchParams() const page = searchParams.get('page') //console.log(page) const currentPage = Number(page) || 1; const { isLoading, data, isError, isFetching, error } = useQuery({ queryKey: ["customers", currentPage], queryFn: () => getCustomerList(currentPage) }); //console.log(data); if (isLoading) return "Loading..."; if (isError) return `Error: ${error.message}`; const totalPages = Math.ceil(Number(data.totalpage) / Number(data.perpage)); //console.log(totalPages); return ( <div className="w-screen py-20 flex justify-center flex-col items-center"> <div className="flex items-center justify-between gap-1 mb-5 pl-10 pr-10"> <h1 className="text-4xl font-bold">Nextjs 15 Python Flask Rest API CRUD (Create, Read, Update and Delete) with Pagination and View | Tanstack Query Tailwind CSS</h1> </div> <div className="overflow-x-auto pt-10"> <div className="mb-2 w-full text-right"> <Link href="/customer/create" className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"> Add New </Link> </div> <CustomerList customerlist={data.customerlist} /> <div className="flex items-center justify-between my-5"> <Paginationnumber totalPages={totalPages} /> {isFetching ? <LoadingSpinner /> : null} </div> </div> </div> ); }app\layout.tsx
//app\layout.tsx import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import QueryProvider from "./components/QueryProvider"; const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], }); const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"], }); export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en"> <body className={`${geistSans.variable} ${geistMono.variable} antialiased`} > <QueryProvider>{children}</QueryProvider> </body> </html> ); }app\components\QueryProvider.tsx
//app\components\QueryProvider.tsx "use client"; import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; import { useState } from "react"; interface Props { children: React.ReactNode; } export default function QueryProvider({ children }: Props) { const [queryClient] = useState(() => new QueryClient()); return ( <QueryClientProvider client={queryClient}> {children} </QueryClientProvider> ); }app\components\CustomerList.tsx
//app\components\CustomerList.tsx import Link from "next/link"; import { useMutation, useQueryClient } from "@tanstack/react-query"; //npm i @tanstack/react-query import { deleteCustomer } from "../api/customer"; const CustomerList = ({ customerlist }) => { const queryClient = useQueryClient(); const deleteCustomerMutation = useMutation({ mutationFn: deleteCustomer, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['customers']}); } }); const handleDelete = (id) => { deleteCustomerMutation.mutate(id) } return ( <> <table className="table table-zebra"> <thead className="text-sm text-gray-700 uppercase bg-gray-50"> <tr> <th className="py-3 px-6">ID</th> <th className="py-3 px-6">Name</th> <th className="py-3 px-6">Email</th> <th className="py-3 px-6">Contact No.</th> <th className="py-3 px-6 text-center">Actions</th> </tr> </thead> <tbody> {customerlist.map((item) => ( <tr key={item.id} className="bg-white border-b text-black"> <td className="py-3 px-6"> {item.id} </td> <td className="py-3 px-6">{item.name}</td> <td className="py-3 px-6">{item.email}</td> <td className="py-3 px-6">{item.contactnumber}</td> <td className="flex justify-center gap-1 py-3"> <Link className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800" href={`/customer/${item.id}`}>Read</Link> <Link className="focus:outline-none text-white bg-yellow-400 hover:bg-yellow-500 focus:ring-4 focus:ring-yellow-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:focus:ring-yellow-900" href={`/customer/edit/${item.id}/`}> Edit </Link> <button onClick={() => handleDelete(item.id)} className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900"> Delete</button> </td> </tr> ))} </tbody> </table> </> ) } export default CustomerListapp\components\LoadingSpinner.tsx
//app\components\LoadingSpinner.tsx export default function LoadingSpinner() { return ( <h1>Loading...</h1> ) }app\customer\create\page.tsx
//app\customer\create\page.tsx "use client"; import { useMutation, useQueryClient } from "@tanstack/react-query" import CustomertForm from "../../components/CustomertForm" import { createCustomer } from "../../api/customer" import { useRouter } from 'next/navigation' const AddCustomer= () => { const queryClient = useQueryClient(); const router = useRouter(); const createUserMutation = useMutation({ mutationFn: createCustomer, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['customers']}); //console.log("success!") router.push('/') } }); const handleAddPost = (customer) => { //console.log(customer) createUserMutation.mutate({ ...customer }) } return ( <div className="max-w-md mx-auto mt-5 mb-5"> <h1 className="text-2xl text-center mb-2">Add New Customer</h1> <CustomertForm onSubmit={handleAddPost} initialValue={{}} /> </div> ) } export default AddCustomerapp\components\CustomertForm.tsx
//app\components\CustomertForm.tsx "use client"; import { useState } from "react" const CustomertForm = ({ onSubmit, initialValue }) => { const [customer, setCustomer] = useState({ name: initialValue.name || "", email: initialValue.email || "", contactnumber: initialValue.contactnumber || "" }); //console.log(customer); const handleChangeInput = (e) => { setCustomer({ ...customer, [e.target.name]: e.target.value }) } const renderField = (label) => ( <div> <label className="block text-sm font-medium text-gray-900">{label}</label> <input onChange={handleChangeInput} type="text" name={label.toLowerCase()} value={customer[label.toLowerCase()]} className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500blue-500" /> </div> ); const handleSubmit = (e) => { e.preventDefault(); onSubmit(customer); setCustomer({ name: "", email: "", contactnumber: "", }) } return ( <form onSubmit={handleSubmit}> <div className="mb-5">{renderField('Name')}</div> <div className="mb-5">{renderField('Email')}</div> <div className="mb-5">{renderField('Contactnumber')}</div> <button type="submit" className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800" > Submit</button> </form> ) } export default CustomertFormapp\customer\[id]\page.tsx
//app\customer\[id]\page.tsx "use client"; import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query import { useParams } from 'next/navigation' import { fetchCustomer } from "../../api/customer"; const ViewCustomer = () => { const {id}=useParams(); const { isLoading, isError, data: customer, error, } = useQuery({ queryKey: ["customer", id], queryFn: () => fetchCustomer(id), }); //console.log(customer); if (isLoading) return "loading..."; if (isError) return `Error: ${error.message}`; //console.log(customer) return ( <div className="min-h-screen flex items-center justify-center bg-slate-100"> <div className="bg-white rounded-sm shadow p-8 text-black"> <div className="max-w-2xl mx-auto mt-5"> <h1 className="text-2xl text-center mb-2">View Customer</h1> <h2>ID : {customer.customer.id}</h2> <h2>Name : {customer.customer.name}</h2> <h2>Email : {customer.customer.email}</h2> <h2>Contact Number : {customer.customer.contactnumber}</h2> </div> </div> </div> ) } export default ViewCustomerapp\customer\edit\[id]\page.tsx
//app\customer\edit\[id]\page.tsx "use client"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useParams, useRouter } from 'next/navigation' import { fetchCustomer, updateCustomer } from "../../../api/customer"; import CustomertForm from "../../../components/CustomertForm" const EditCustomer = () => { const {id}=useParams(); //console.log(id); const queryClient = useQueryClient(); const router = useRouter(); const { isLoading, isError, data: customer, error, } = useQuery({ queryKey: ["customers", id], queryFn: () => fetchCustomer(id), }); console.log(customer); const updateCustomerMutation = useMutation({ mutationFn: updateCustomer, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['customers']}); console.log("success"); router.push('/') } }) if (isLoading) return "loading..."; if (isError) return `Error: ${error.message}`; const handleSubmit = (updatedCustomer) => { updateCustomerMutation.mutate({id, ...updatedCustomer}) } return ( <div className="max-w-md mx-auto mt-5"> <div className="flex items-center justify-between gap-1 mb-5"> <h1 className="text-4xl font-bold">{customer.customer.name}</h1> </div> <CustomertForm onSubmit={handleSubmit} initialValue={customer.customer} /> </div> ) } export default EditCustomerapp\components\Paginationnumber.tsx
//app\components\Paginationnumber.tsx "use client"; import Link from "next/link"; import { HiChevronLeft, HiChevronRight } from "react-icons/hi"; //npm install react-icons --save npmjs com/package/react-icons import { usePathname, useSearchParams } from "next/navigation"; import clsx from "clsx"; //npm i clsx npmjs com/package/clsx export const generatePagination = (currentPage: number, totalPages: number) => { if (totalPages <= 7) { return Array.from({ length: totalPages }, (_, i) => i + 1); } if (currentPage <= 3) { return [1, 2, 3, "...", totalPages - 1, totalPages]; } if (currentPage >= totalPages - 2) { return [1, 2, 3, "...", totalPages - 2, totalPages - 1, totalPages]; } return [ 1, "...", currentPage - 1, currentPage, currentPage + 1, "...", totalPages, ]; }; const Paginationnumber = ({ totalPages }: { totalPages: number }) => { const pathname = usePathname(); const searchParams = useSearchParams(); const currentPage = Number(searchParams.get("page")) || 1; const createPageURL = (pageNumber: string | number) => { const params = new URLSearchParams(searchParams); params.set("page", pageNumber.toString()); return `${pathname}?${params.toString()}`; }; const allPages = generatePagination(currentPage, totalPages); const PaginationNumber = ({ page, href, position, isActive, }: { page: number | string; href: string; position?: "first" | "last" | "middle" | "single"; isActive: boolean; }) => { const className = clsx( "flex h-10 w-10 items-center justify-center text-sm border", { "rounded-l-sm": position === "first" || position === "single", "rounded-r-sm": position === "last" || position === "single", "z-10 bg-blue-700 border-blue-500 text-white bg-blue-700": isActive, "hover:bg-blue-700": !isActive && position !== "middle", "text-gray-300 pointer-events-none": position === "middle", } ); return isActive && position === "middle" ? ( <div className={className}>{page}</div> ) : ( <Link href={href} className={className}> {page} </Link> ); }; const PaginationArrow = ({ href, direction, isDisabled, }: { href: string; direction: "left" | "right"; isDisabled?: boolean; }) => { const className = clsx( "flex h-10 w-10 items-center justify-center text-sm border", { "pointer-events-none text-blue-300": isDisabled, "hover:bg-blue-700": !isDisabled, "mr-2": direction === "left", "ml-2": direction === "right", } ); const icon = direction === "left" ? ( <HiChevronLeft size={20} /> ) : ( <HiChevronRight size={20} /> ); return isDisabled ? ( <div className={className}>{icon}</div> ) : ( <Link href={href} className={className}> {icon} </Link> ); }; return ( <div className="inline-flex"> <PaginationArrow direction="left" href={createPageURL(currentPage - 1)} isDisabled={currentPage <= 1} /> <div className="flex -space-x-px"> {allPages.map((page, index) => { let position: "first" | "last" | "single" | "middle" | undefined; if (index === 0) position = "first"; if (index === allPages.length - 1) position = "last"; if (allPages.length === 1) position = "single"; if (page === "...") position = "middle"; return ( <PaginationNumber key={index} href={createPageURL(page)} page={page} position={position} isActive={currentPage === page} /> ); })} </div> <PaginationArrow direction="right" href={createPageURL(currentPage + 1)} isDisabled={currentPage >= totalPages} /> </div> ); }; export default Paginationnumber;app\api\customer.tsx
//app\api\customer.tsx export async function getCustomerList(page) { const response = await fetch(`http://127.0.0.1:5000/api/customerpagination/${page}/3`); const data = await response.json(); //console.log(data); return { customerlist: data.customers, totalpage: data.total, perpage: data.per_page } } export async function fetchCustomer(id) { const response = await fetch("http://127.0.0.1:5000/api/customer/"+id); return response.json() } export async function createCustomer(newCustomer) { const response = await fetch("http://127.0.0.1:5000/api/customer", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(newCustomer) }); console.log(response); return response.json() } export async function updateCustomer(updatedCustomer) { const response = await fetch(`http://127.0.0.1:5000/api/customer/${updatedCustomer.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(updatedCustomer) }); console.log(response) return response.json() } export async function deleteCustomer(id) { const response = await fetch(`http://127.0.0.1:5000/api/customer/${id}`, { method: "DELETE", }); return response.json() }run C:\nextjs>npm run dev
python-guide-es.readthedocs.io/es/guide-es/dev/virtualenvs.html
Create an environment
ednalan@Cairocoders myapp % pip install virtualenv
ednalan@Cairocoders myapp % pip install virtualenv
Activate the environment
ednalan@Cairocoders myapp % source venv/bin/activate
(venv) ednalan@Cairocoders myapp %
Install Flask
pypi.org/project/Flask/
(venv) ednalan@Cairocoders myapp % pip install -U Flask
(venv) ednalan@Cairocoders myapp % flask run
Install requirements
pip install -U flask-cors
pypi.org/project/Flask-Cors/
Flask-SQLAlchemy
Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application.
flask-sqlalchemy.palletsprojects.com/en/3.0.x/
(venv) PS C:\flask_dev\myapp>pip install -U Flask-SQLAlchemy
Flask + marshmallow for beautiful APIs
pypi.org/project/flask-marshmallow/
(venv) PS C:\flask_dev\myapp>pip install flask-marshmallow
python3 -m pip install
https://pypi.org/project/pymysql/
app.py
//app.py from flask import Flask, request, jsonify, make_response from flask_cors import CORS, cross_origin #ModuleNotFoundError: No module named 'flask_cors' = pip install Flask-Cors from models import db, Customer app = Flask(__name__) app.config['SECRET_KEY'] = 'cairocoders-ednalan' #app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///flaskdb.db' # Databse configuration mysql Username:password@hostname/databasename app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:root@localhost:8889/nextjsdb' SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_ECHO = True CORS(app, supports_credentials=True) db.init_app(app) with app.app_context(): db.create_all() # create a test route @app.route('/test', methods=['GET']) def test(): return jsonify({'message': 'The server is running'}) # create a customer @app.route('/api/customer', methods=['POST']) def create_customer(): try: data = request.get_json() new_customer = Customer(name=data['name'], email=data['email'], contactnumber=data['contactnumber']) db.session.add(new_customer) db.session.commit() return jsonify({ 'id': new_customer.id, 'name': new_customer.name, 'email': new_customer.email, 'contactnumber': new_customer.contactnumber }), 201 except Exception as e: return make_response(jsonify({'message': 'error creating customer', 'error': str(e)}), 500) # get all customer @app.route('/api/customer', methods=['GET']) def get_customer(): try: customer = Customer.query.all() customer_data = [{'id': customer.id, 'name': customer.name, 'email': customer.email} for customer in users] return jsonify(customer_data), 200 except Exception as e: return make_response(jsonify({'message': 'error getting customer', 'error': str(e)}), 500) @app.route('/api/customerpagination/<int:page>/<int:per_page>', methods=['GET']) def customerpagination(page=1, per_page=3): total = Customer.query.count() print(total) customers = Customer.query.order_by(Customer.id.asc()) customers = customers.paginate(page=page, per_page=per_page) return jsonify({ 'total': total, 'page': page, 'per_page': per_page, 'has_next': customers.has_next, 'has_prev': customers.has_prev, 'page_list': [iter_page if iter_page else '...' for iter_page in customers.iter_pages()], 'customers': [{ 'id': p.id, 'name': p.name, 'email': p.email, 'contactnumber': p.contactnumber, } for p in customers.items] }) # get a customers by id @app.route('/api/customer/<id>', methods=['GET']) def get_customerid(id): try: customer = Customer.query.filter_by(id=id).first() # get the first customer with the id if customer: return make_response(jsonify({'customer': customer.json()}), 200) return make_response(jsonify({'message': 'customer not found'}), 404) except Exception as e: return make_response(jsonify({'message': 'error getting customer', 'error': str(e)}), 500) # update a customer by id @app.route('/api/customer/<id>', methods=['PUT']) def update_customer(id): try: customer = Customer.query.filter_by(id=id).first() if customer: data = request.get_json() customer.name = data['name'] customer.email = data['email'] customer.contactnumber = data['contactnumber'] db.session.commit() return make_response(jsonify({'message': 'customer updated'}), 200) return make_response(jsonify({'message': 'customer not found'}), 404) except Exception as e: return make_response(jsonify({'message': 'error updating customer', 'error': str(e)}), 500) # delete a customer by id @app.route('/api/customer/<id>', methods=['DELETE']) def delete_customer(id): try: customer = Customer.query.filter_by(id=id).first() if customer: db.session.delete(customer) db.session.commit() return make_response(jsonify({'message': 'customer deleted'}), 200) return make_response(jsonify({'message': 'customer not found'}), 404) except Exception as e: return make_response(jsonify({'message': 'error deleting customer', 'error': str(e)}), 500) if __name__ == "__main__": app.run(debug=True)models.py
//models.py from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() class Customer(db.Model): __tablename__ = 'customer' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(80), unique=True, nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) contactnumber = db.Column(db.String(80), unique=True, nullable=False) def json(self): return {'id': self.id,'name': self.name, 'email': self.email, 'contactnumber': self.contactnumber}run (venv) C:\flask_dev\myapp>flask run