38
loading...
This website collects cookies to deliver better user experience
/YOUR_COLLECTION_s
: Creates new content./YOUR_COLLECTION_s
: Gets all the contents./YOUR_COLLECTION_s/:ID
: Gets a single content based on its ID./YOUR_COLLECTION_s/:ID
: Edits a content/YOUR_COLLECTION_s/:ID
: Deletes a content.npx create-strapi-app strapi-api
# OR
yarn create strapi-api strapi-api
yarn develop
command to start the server at localhost:1337
. The API endpoints are consumed from the localhost:1337
URL. Also, we can load the admin UI from the same URL at localhost:1337/admin
.strapi-API/config/
folder.config/
functions/
responses/
404.js
bootstrap.js
cron.js
database.js
server.js
404.js
file is used to return a custom 404 message."use strict";
module.exports = async (/* ctx */) => {
// return ctx.notFound('My custom message 404');
};
cron.js
file is where we can set our cron jobs on Strapi. These jobs are scheduled to run periodically based on the format we input: [SECOND (optional)] [MINUTE] [HOUR] [DAY OF MONTH] [MONTH OF YEAR] [DAY OF WEEK]
."use strict";
module.exports = {};
server.js
is where we configure the Strapi server. We can set our host, port, and authentication. Strapi, by default, serves at 0.0.0.0
at port 1337. We can change them in this file.module.exports = ({ env }) => ({
host: env("HOST", "0.0.0.0"),
port: env.int("PORT", 1337),
admin: {
auth: {
secret: env("ADMIN_JWT_SECRET", "9c8eb554ab87ab3e95d1e32ca140e304"),
},
},
});
database.js
is where is the database to use is configured. The database's client, hostname, port, etc., are set here.module.exports = ({ env }) => ({
defaultConnection: "default",
connections: {
default: {
connector: "bookshelf",
settings: {
client: "sqlite",
filename: env("DATABASE_FILENAME", ".tmp/data.db"),
},
options: {
useNullAsDefault: true,
},
},
},
});
defaultConnection
is the default connection for models to use in querying the database. The default value is the default. You can set it to any property keys in the connections object.connections
set the type of connections we can use to access a database. We have a default connection that connects to an SQLite database.client
is the database client to create the connection.filename
is the path to the database file.file
allows us to set our Strapi app to use PostgreSQL, MySQL, SQLServer, or MongoDB.bank
because we will be building a bank app to demonstrate further how to use PostgreSQL DB with Strapi.strapi-api/config/database.js
file.strapi-api/config/database.js
and paste the below code in the file:// strapi-api/config/database.js
module.exports = ({ env }) => ({
defaultConnection: "default",
connections: {
default: {
connector: "bookshelf",
settings: {
client: "postgres",
host: env("DATABASE_HOST", "localhost"),
port: env.int("DATABASE_PORT", 5432),
database: env("DATABASE_NAME", "bank"),
username: env("DATABASE_USERNAME", "postgres"),
password: env("DATABASE_PASSWORD", "0000"),
schema: env("DATABASE_SCHEMA", "public"),
},
options: {},
},
},
});
connections
object, we have only a default
connection.default
connection, we set the connector
to the bookshelf.settings
, we set the client
to Postgres
. This client is the PostgresSQL database client to create the connection to the DB.host
is the hostname of the PostgreSQL server we set it to localhost
.port
is set to 5432, and this is the default port of the PostgreSQL server.name
is set to the bank, and this is the name of the database we created in the PostgreSQL server.password
is the password of our PostgreSQL server.username
is the username of our PostgreSQL. It is set to Postgres
because it is the username of our PostgreSQL server.schema
is the database schema, and it is set to the public
here. This schema is used to expose databases to the public.yarn develop
localhost:1337/admin
on our browser. Now register and click on the LET'S START
button, this will take you to the admin panel.Account {
name
balance
}
name
field will hold the name of the account holder.balance
will hold the balance of the account holder in Dollars.Transact {
sender
receiver
amount
}
sender
field holds the name of the account holder that transfers the money.receiver
is the beneficiary.amount
is the amount the sender sends to the receiver.Account
model.Create First Content Type
button and type in "account" for a collection name.account
collection:+ Add another field
button and select Text
and type in name
, and then click on the + Add another field
button to add another field.Number
and on the Number format
select float (ex. 3.3333333)
, then type in balance
and click on the Finish
button.Account
page that appears click on the Save
button that is on the top-right corner of the page.Transact
collection:+ Create new collection type
link, a modal will show up, type in transact
. Click on the + Add another field
button.sender
, receiver
, and amount
. The fields sender
and receiver
will be Text
fields while amount
will be a Number
field with float (ex. 3.333333)
Number format.Finish
button and the Save
button./transfer
API, a POST method. A transfer HTTP request will look like this:http://localhost:1337/transfer
Method: POST
Body:
{
sender: nnamdi
receiver: chidme
amount: 10
}
api
folder. So we go to our api
folder, we will see folders created for our APIs: transact
and account
.strapi-api
api/
account/
config/
routes.json
controllers/
account.js
models/
services/
transact/
...
routes.json
file contains the endpoints contained in an API.controllers
folder contains files that the user can use to customize the endpoints in an API. The user can apply his logic for an endpoint.transfer
API.transfer
folder in our api
folder:mkdir transfer
config
and controllers
folders inside the transfer
folder.mkdir transfer/config transfer/controllers
routes.json
file inside the config
folder:touch transfer/config/routes.json
/transfer
endpoint and to be on POST HTTP method. Then, we will make the handler point to an index
function that will export from controllers
.{
"routes": [
{
"method": "POST",
"path": "/transfer",
"handler": "Transfer.index",
"config": {
"policies": []
}
}
]
}
transfer
file in the controllers
folder.touch transfer/controllers/transfer.js
index
function. This function will be called when the localhost:1337/transfer
HTTP request is made. The function will handle that request. This is where we will apply our business logic of sending money from an account to another beneficiary account.// strapi-api/api/transfer/controllers/transfer.js
const { sanitizeEntity } = require("strapi-utils");
module.exports = {
async index(ctx) {
const { sender, receiver, amount } = ctx.request.body;
let entity;
// deduct amount from sender
// add amount to reciver
// add the transaction to transact
const senderAcc = await strapi.services.account.findOne({
name: sender,
});
const receiverAcc = await strapi.services.account.findOne({
name: receiver,
});
senderAcc.balance = parseFloat(senderAcc.balance) - parseFloat(amount);
receiverAcc.balance = parseFloat(receiverAcc.balance) + parseFloat(amount);
await strapi.services.account.update({ name: sender }, senderAcc);
await strapi.services.account.update({ name: receiver }, receiverAcc);
entity = await strapi.services.transact.create({
sender,
receiver,
amount,
});
return sanitizeEntity(entity, { model: strapi.models.transact });
},
};
ctx
holds the res
and req
just like in Expressjs or Koajs. The ctx
is an object that contains properties and methods for accessing the incoming message and for responding to the client.sender
, receiver
, and amount
from the ctx.request.body
..services
object, which contains methods to access the database. See the functions in it: create
, update
, find
, findOne
, etc. They are used to create data in the database, update the database, retrieve values from the database.amount
from the sender's balance, and added the receiver's balance.transact
table, and finally, we returned the result of the new transaction.sanitizeEntity
function removes all private fields from the model and its relations.transfer
API appear on the admin panel, and it is a standalone API, not a collection type.Settings
item on the sidebar menu, then on the Roles
item on the second sidebar menu that appears. On the right section, click on the Public
item and scroll down.Select all
checkbox and click on the Save
button at the top. This will allow public access to all the APIs in our Strapi project:Accounts
in the sidebar. Click on the + Add New Accounts
button.name -> nnamdi
balance -> 2000000
Save
button and the Publish
button.name -> chidume
balance -> 1000000
Save
button and the Publish
button.yarn create next-app strapi-bank
/
/account/[id]
/
route will display all the accounts on the system./account/[id]
route will display a particular account details. This is a dynamic route, the id
can hold any value, its dynamic, and it will be the unique id of an account.Header
: This will render the Header.AccountCard
: This component will display a few of the account details in the /
route.AddAccountDialog
: This is a dialog that renders the UI we will use to add new accounts to the system.TransactionDialog
: This dialog renders UI where transactions will be made, sending money from one Account to another.TransactionCard
: This component will render the transactions of a user.Accounts
: This is the page component for the /
page. It displays all the accounts in the bank.Account
: This is the page component for the /account/[id]
page.mkdir components
mkdir components/TransactionCard
touch components/TransactionCard/index.js
touch components/TransactionCard/TransactionCard.module.css
mkdir components/TransactionDialog
touch components/TransactionDialog/index.js
mkdir components/AddAccountDialog
touch components/AddAccountDialog/index.js
mkdir components/AccountCard
touch components/AccountCard/index.js
touch components/AccountCard/AccountCard.module.css
mkdir components/Header
touch components/Header/index.js
touch components/Header/Header.module.css
touch styles/AccountView.module.css
mkdir pages/account
touch pages/account/[id].js
Bank Admin
. Paste the below code on components/Header/index.js
:import { header, headerName } from "./Header.module.css";
export default function Header() {
return (
<section className={header}>
<div className={headerName}>Bank Admin</div>
</section>
);
}
Accounts
component. It will display a mini detail of an account.components/AccountCard/index.js
:import styles from "./AccountCard.module.css";
import Link from "next/link";
export default function AccountCard({ account }) {
const { id, name, balance, created_at } = account;
return (
<Link href={`account/${id}`}>
<div className={styles.account}>
<div className={styles.accountdetails}>
<div className={styles.accountname}>
<h3>
<span style={{ fontWeight: "100" }}>Account: </span>
{name}
</h3>
</div>
<div className={styles.accountbalance}>
<span>
<span style={{ fontWeight: "100" }}>Balance($): </span>
{balance}
</span>
</div>
<div className={styles.accountcreated_at}>
<span>Created: {created_at}</span>
</div>
</div>
</div>
</Link>
);
}
account
object in its props
argument. Next, we destructure id
, name
, balance
, created_at
from the account
object.id
and created_at
are fields set by Strapi in each model content.AccountCard
component renders the details.sender
, receiver
, and the amount
sent. The Account page component renders this component to show the transactions done by an account user—the debits and credits.components/TransactionCard/index.js
:import styles from "./TransactionCard.module.css";
export default function TransactionCard({ transaction }) {
const { sender, receiver, amount, created_at } = transaction;
return (
<div className={styles.transactionCard}>
<div className={styles.transactionCardDetails}>
<div className={styles.transactionCardName}>
<h4>
<span>Sender: </span>
<span style={{ fontWeight: "bold" }}>{sender}</span>
</h4>
</div>
<div className={styles.transactionCardName}>
<h4>
<span>Receiver: </span>
<span style={{ fontWeight: "bold" }}>{receiver}</span>
</h4>
</div>
<div className={styles.transactionCardName}>
<h4>
<span>Amount($): </span>
<span style={{ fontWeight: "bold" }}>{amount}</span>
</h4>
</div>
<div className={styles.transactionCardName}>
<h4>
<span>Created At: </span>
<span style={{ fontWeight: "bold" }}>{created_at}</span>
</h4>
</div>
</div>
</div>
);
}
transaction
object in its props. The fields sender
, receiver
, amount
, created_at
are destructured from the transaction
object. These are then rendered by the component./
route is navigated. This component will make an HTTP request to the Strapi backend to retrieve the list of accounts and render them.pages/index.js
:import Head from "next/head";
import styles from "../styles/Home.module.css";
import Header from "../components/Header";
import AccountCard from "../components/AccountCard";
import { useEffect, useState } from "react";
import axios from "axios";
import TransactDialog from "../components/TransactDialog";
import AddAccountDialog from "../components/AddAccountDialog";
export default function Home() {
const [accounts, setAccounts] = useState([]);
const [showTransactModal, setShowTransactModal] = useState(false);
const [showAddAccountModal, setShowAddAccountModal] = useState(false);
useEffect(async () => {
const data = await axios.get("http://localhost:1337/accounts");
setAccounts(data?.data);
}, []);
return (
<div className={styles.container}>
<Head>
<title>Bank Admin</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<div className={styles.breadcrumb}>
<div>
<span style={{ margin: "1px" }}>
<button onClick={() => setShowTransactModal(true)}>
Transact
</button>
</span>
<span style={{ margin: "1px" }}>
<button onClick={() => setShowAddAccountModal(true)}>
Add Account
</button>
</span>
</div>
</div>
<div className={styles.accountcontainer}>
<div className={styles.youraccounts}>
<h3>Accounts</h3>
</div>
<div>
{accounts.map((account, i) => (
<AccountCard key={i} account={account} />
))}
</div>
</div>
{showAddAccountModal ? (
<AddAccountDialog
closeModal={() => setShowAddAccountModal((pV) => !pV)}
/>
) : null}
{showTransactModal ? (
<TransactDialog
closeModal={() => setShowTransactModal((pV) => !pV)}
/>
) : null}
</main>
</div>
);
}
accounts
: is a state that holds the accounts retrieved from the /accounts
endpoint. showTransactModal
: This is a boolean state that toggles the visibility of the TransactionModal
.showAddAccountModal
: this is also a boolean state used to display and remove the AddAccountModal
.useEffect
callback calls the /accounts
endpoint, and the result is set in the accounts
state.accounts
array is rendered and each account is rendered by the AccountCard
component, each account is passed to the AccountCard
via its account
props. AddAccountDialog
and TransactDialog
dialog components. The Transact
button toggles the TransactDialog
and the Add Account
button toggles the AddAccountDialog
.closeModal
props. The function will enable the dialogs to close themselves from their components./account/[id]
route is navigated.pages/account/[id].js
:import styles from "../../styles/AccountView.module.css";
import { useRouter } from "next/router";
import TransactionCard from "../../components/TransactionCard";
import axios from "axios";
import { useEffect, useState } from "react";
export default function Account() {
const router = useRouter();
const {
query: { id },
} = router;
const [account, setAccount] = useState();
const [transactions, setTransactions] = useState([]);
useEffect(async () => {
const AccountData = await axios.get("http://localhost:1337/accounts/" + id);
var transactsData = await axios.get("http://localhost:1337/transacts");
transactsData = transactsData?.data?.filter(
(tD) =>
tD.sender == AccountData?.data?.name ||
tD.receiver == AccountData?.data?.name
);
console.log(transactsData);
setAccount(AccountData?.data);
setTransactions(transactsData);
}, [id]);
async function deleteAccount() {
if (confirm("Do you really want to delete this account?")) {
await axios.delete("http://localhost:1337/accounts/" + id);
router.push("/");
}
}
return (
<div className={styles.accountviewcontainer}>
<div className={styles.accountviewmain}>
<div style={{ width: "100%" }}>
<div className={styles.accountviewname}>
<h1>{account?.name}</h1>
</div>
<div className={styles.accountviewminidet}>
<div>
<span style={{ marginRight: "4px", color: "rgb(142 142 142)" }}>
Balance($):
</span>
<span style={{ fontWeight: "600" }}>{account?.balance}</span>
</div>
<div style={{ padding: "14px 0" }}>
<span>
<button onClick={deleteAccount} className="btn-danger">
Delete
</button>
</span>
</div>
</div>
<div className={styles.accountviewtransactionscont}>
<div className={styles.accountviewtransactions}>
<h2>Transactions</h2>
</div>
<div className={styles.accountviewtransactionslist}>
{!transactions || transactions?.length <= 0
? "No transactions yet."
: transactions?.map((transaction, i) => (
<TransactionCard key={i} transaction={transaction} />
))}
</div>
</div>
</div>
</div>
</div>
);
}
id
from the URL. We have states account
and transactions
, that hold the account and its transactions respectively.useEffect
hook callback calls the /accounts/" + id
endpoint with the id
value to get the account via its id. Next, it calls the /transacts
endpoint to retrieve the transactions and filter out the transaction made or received by the current account user. The result is set in the transactions
state while the account details are set in the account
state.Delete
button that when clicked deletes the current account user. It does this by calling the endpoint /accounts/" + id
over the DELETE HTTP method with the account's id. This makes Strapi delete the account.components/AddAccountDialog/index.js
:import { useState } from "react";
import EpisodeCard from "../TransactionCard";
import axios from "axios";
export default function AddAccountDialog({ closeModal }) {
const [disable, setDisable] = useState(false);
async function addAccount() {
setDisable(true);
const accountName = window.accountName.value;
const accountBalance = window.accountBalance.value;
// add account
await axios.post("http://localhost:1337/accounts", {
name: accountName,
balance: parseFloat(accountBalance),
});
setDisable(false);
closeModal();
location.reload();
}
return (
<div className="modal">
<div className="modal-backdrop" onClick={closeModal}></div>
<div className="modal-content">
<div className="modal-header">
<h3>Add New Account</h3>
<span
style={{ padding: "10px", cursor: "pointer" }}
onClick={closeModal}
>
X
</span>
</div>
<div className="modal-body content">
<div style={{ display: "flex", flexWrap: "wrap" }}>
<div className="inputField">
<div className="label">
<label>Name</label>
</div>
<div>
<input id="accountName" type="text" />
</div>
</div>
<div className="inputField">
<div className="label">
<label>Balance($):</label>
</div>
<div>
<input id="accountBalance" type="text" />
</div>
</div>
</div>
</div>
<div className="modal-footer">
<button
disabled={disable}
className="btn-danger"
onClick={closeModal}
>
Cancel
</button>
<button disabled={disable} className="btn" onClick={addAccount}>
Add Account
</button>
</div>
</div>
</div>
);
}
Add Account
button when clicked calls the addAccount
function. This function retrieves the account name and balance and calls the /accounts
endpoint via the POST HTTP with the payload: account name and balance. This creates a new account with this payload.components/TransactionDialog/index.js
:import { useState } from "react";
import TransactionCard from "../TransactionCard";
import axios from "axios";
export default function TransactDialog({ closeModal }) {
const [disable, setDisable] = useState(false);
async function transact() {
setDisable(true);
const sender = window.sender.value;
const receiver = window.receiver.value;
const amount = window.amount.value;
await axios.post("http://localhost:1337/transfer", {
sender,
receiver,
amount,
});
setDisable(false);
closeModal();
location.reload();
}
return (
<div className="modal">
<div className="modal-backdrop" onClick={closeModal}></div>
<div className="modal-content">
<div className="modal-header">
<h3>Transaction</h3>
<span
style={{ padding: "10px", cursor: "pointer" }}
onClick={closeModal}
>
X
</span>
</div>
<div className="modal-body content">
<div style={{ display: "flex", flexWrap: "wrap" }}>
<div className="inputField">
<div className="label">
<label>Sender</label>
</div>
<div>
<input id="sender" type="text" />
</div>
</div>
<div className="inputField">
<div className="label">
<label>Receiver</label>
</div>
<div>
<input id="receiver" type="text" />
</div>
</div>
<div className="inputField">
<div className="label">
<label>Amount($)</label>
</div>
<div>
<input id="number" id="amount" type="text" />
</div>
</div>
</div>
</div>
<div className="modal-footer">
<button
disabled={disable}
className="btn-danger"
onClick={closeModal}
>
Cancel
</button>
<button disabled={disable} className="btn" onClick={transact}>
Transact
</button>
</div>
</div>
</div>
);
}
transact
function does the job. It retrieves the sender, receiver, and amount values from the input boxes, and then calls the endpoint /transfer
via HTTP POST passing in the sender, receiver, and amount as payload. The /transfer
endpoint will then transfer the amount
from the sender
to the receiver
.