Developer Guide

Backend Developer Guide:

Introduction

This technical documentation outlines the core features and functionalities of the system, providing developers with an overview and detailed guidance for implementation. The system is designed to offer seamless user management, data security, and efficient, Benefit exploring and connection with the UBI network(ONSET) and application handling.

Architecture

Key Features

  • Data storage

    • Postgres 17 stores user data, user documents, user applications, user consent, and user roles.

  • User authentication & authorization

    • Keyclock is used for IAM along with user authentication & authorization. Keyclock is also used for the registration & login process.

  1. Import documents from E-Wallet Documents can be imported from e-wallet.

  2. Eligibility criteria filter Users are shown benefits they are eligible to based on their profile.

  3. Apply to benefits Users can apply for benefits provided by different providers. The application process runs through the ONEST network.

  4. Application tracking The status of each user application is tracked through ONEST APIs.

  5. Data privacy & security Sensitive information such as aadhaar number is stored in encrypted format. User documents are also stored in encrypted format.

Modules

1. Auth: For user registration, login and logout APIs 2. OTP: Contains APIs for sending OTP and verifying OTP 3. Users: Contains APIs required for user experience

Services

1. Keycloak: Contains all the functions related to keycloak such as CRUD users in keycloak, get keycloak admin token, get user token etc.

2. Proxy: For calling ONEST-related APIs

3. Hasura:

APIs

1. register_with_password: Register a new user on ‘username’-’password’ basis.

curl --location 'https://your-beneficary-api-domain.com/api/auth/register_with_password' \
--header 'Accept: application/json, text/plain, */*' \
--header 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/json' \
--header 'Origin: http://localhost:5173' \
--header 'Referer: http://localhost:5173/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: cross-site' \
--header 'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36' \
--header 'sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"' \
--header 'sec-ch-ua-mobile: ?1' \
--header 'sec-ch-ua-platform: "Android"' \
--data '{
    "firstName": "Yash",
    "lastName": "Gavai",
    "phoneNumber": "1324567890",
    "password": "123456"
}'

2. login: Login to the beneficiary app

curl --location 'https://your-beneficary-api-domain.com/api/auth/login' \
--header 'Accept: application/json, text/plain, */*' \
--header 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/json' \
--header 'Origin: http://localhost:5173' \
--header 'Referer: http://localhost:5173/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: cross-site' \
--header 'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36' \
--header 'sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"' \
--header 'sec-ch-ua-mobile: ?1' \
--header 'sec-ch-ua-platform: "Android"' \
--data '{"username":"","password":""}'

3. logout: logout of app

curl --location 'https://your-beneficary-api-domain.com/api/auth/logout' \
--header 'Accept: application/json, text/plain, */*' \
--header 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/json' \
--header 'Origin: http://localhost:5173' \
--header 'Referer: http://localhost:5173/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: cross-site' \
--header 'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36' \
--header 'sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"' \
--header 'sec-ch-ua-mobile: ?1' \
--header 'sec-ch-ua-platform: "Android"' \
--data '{"access_token":<access_token>,"refresh_token":<refresh_token>}'

4. get_my_consents:

curl --location 'https://your-beneficary-api-domain.com/api/users/get_my_consents' \
--header 'Accept: */*' \
--header 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
--header 'Authorization: Bearer <token>' \
--header 'Connection: keep-alive' \
--header 'If-None-Match: W/"1ed-1j9BkBZ1ZAkJwId4k8fcQL34/JM"' \
--header 'Origin: http://localhost:5173' \
--header 'Referer: http://localhost:5173/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: cross-site' \
--header 'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36' \
--header 'sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"' \
--header 'sec-ch-ua-mobile: ?1' \
--header 'sec-ch-ua-platform: "Android"'

5. consent:

curl --location 'https://your-beneficary-api-domain.com/api/users/consent' \
--header 'Accept: */*' \
--header 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
--header 'Authorization: Bearer <token>' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/json' \
--header 'Origin: http://localhost:5173' \
--header 'Referer: http://localhost:5173/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: cross-site' \
--header 'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36' \
--header 'sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"' \
--header 'sec-ch-ua-mobile: ?1' \
--header 'sec-ch-ua-platform: "Android"' \
--data '{"user_id":"4d37-9f87-217f828afc43","purpose":"sign_up_tnc","purpose_text":"sign_up_tnc","accepted":true}'

6. get_one:

curl --location 'https://your-beneficary-api-domain.com/api/users/get_one/?decryptData=true' \
--header 'Accept: application/json' \
--header 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
--header 'Authorization: Bearer <token>' \
--header 'Connection: keep-alive' \
--header 'If-None-Match: W/"43c-WpEjzpeZc92dbz32ki5lJAM1tfU"' \
--header 'Origin: http://localhost:5173' \
--header 'Referer: http://localhost:5173/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: cross-site' \
--header 'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36' \
--header 'sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"' \
--header 'sec-ch-ua-mobile: ?1' \
--header 'sec-ch-ua-platform: "Android"'

7. user_docs:

This API is used when importing documents from the

wallet. User documents are passed in the request body as an array. Each

document must contain doc_type, doc_subtype, doc_name, doc_data,

imported_from and doc_datatype. doc_data is encrypted before saving

to the database. After documents are saved successfully, user profile

fields are updated based on respective documents.

curl --location 'https://your-beneficary-api-domain.com/api/users/wallet/user_docs' \
--header 'Accept: application/json, text/plain, */*' \
--header 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
--header 'Authorization: Bearer <token>' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/json' \
--header 'Origin: http://localhost:5173' \
--header 'Referer: http://localhost:5173/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: cross-site' \
--header 'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36' \
--header 'sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"' \
--header 'sec-ch-ua-mobile: ?1' \
--header 'sec-ch-ua-platform: "Android"' \
--data-raw '[
    {
        "doc_name": "Aadhaar Card",
        "doc_type": "idProof",
        "doc_subtype": "aadhaar",
        "doc_data": {
<Adhaar_Data>
        },
        "uploaded_at": "2024-12-03T12:57:45.557Z",
        "imported_from": "e-wallet",
        "doc_datatype": "Application/JSON"
    }
]'

8. user_applications_list:

curl --location 'https://your-beneficary-api-domain.com/api/users/user_applications_list' \
--header 'Accept: application/json' \
--header 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
--header 'Authorization: Bearer <token>' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/json' \
--header 'Origin: http://localhost:5173' \
--header 'Referer: http://localhost:5173/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: cross-site' \
--header 'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36' \
--header 'sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"' \
--header 'sec-ch-ua-mobile: ?1' \
--header 'sec-ch-ua-platform: "Android"' \
--data '{"filters":{"user_id":"605bb1ee-e6d4-400c-b52e-0ff17da12bd3","benefit_id":"PB-BTR-2024-12-02-000726"}}'

9. user_application (Get method):

curl --location 'https://your-beneficary-api-domain.com/api/users/user_application/be0e44f7-fc60-4fad-b7ae-e603eb8b2ef2' \
--header 'Accept: */*' \
--header 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
--header 'Authorization: Bearer <token>' \
--header 'Connection: keep-alive' \
--header 'If-None-Match: W/"e2aa6-4oHe1n4lnN+LC4qsdUGjed4HlFs"' \
--header 'Origin: http://localhost:5173' \
--header 'Referer: http://localhost:5173/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: cross-site' \
--header 'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36' \
--header 'sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"' \
--header 'sec-ch-ua-mobile: ?1' \
--header 'sec-ch-ua-platform: "Android"'

10. user_application (Post method):

curl --location 'https://your-beneficary-api-domain.com/api/users/user_application' \
--header 'Accept: application/json, text/plain, */*' \
--header 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
--header 'Authorization: Bearer <token>' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/json' \
--header 'Origin: http://localhost:5173' \
--header 'Referer: http://localhost:5173/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: cross-site' \
--header 'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36' \
--header 'sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"' \
--header 'sec-ch-ua-mobile: ?1' \
--header 'sec-ch-ua-platform: "Android"' \
--data '{
  
        <Application_Data>
    
}'

1. Search benefits 2. Select Benefit

3. Apply to benefit 4. Confirm application 5. Track application status

  • For the above 5 APIs understanding of ONEST APIs is required, refer to the following document:

Tech Documentation for ONEST Network

12. document_list:

curl --location 'https://your-beneficary-api-domain.com/api/content/documents_list' \
--header 'Accept: */*' \
--header 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
--header 'Authorization: Bearer <token>' \
--header 'Connection: keep-alive' \
--header 'If-None-Match: W/"2de-nf2hEgiRb8d1mSVxED0TIUOO+qo"' \
--header 'Origin: http://localhost:5173' \
--header 'Referer: http://localhost:5173/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: cross-site' \
--header 'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36' \
--header 'sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"' \
--header 'sec-ch-ua-mobile: ?1' \
--header 'sec-ch-ua-platform: "Android"'

13. search:

curl --location 'https://your-beneficary-api-domain.com/api/content/search' \
--header 'Accept: application/json, text/plain, */*' \
--header 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/json' \
--header 'Origin: http://localhost:5173' \
--header 'Referer: http://localhost:5173/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: cross-site' \
--header 'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36' \
--header 'sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"' \
--header 'sec-ch-ua-mobile: ?1' \
--header 'sec-ch-ua-platform: "Android"' \
--data '{"filters":{"caste":"","annualIncome":""},"search":""}'

14. send_otp: This API is used to send OTP to a given phone number.

This API expects phone number with country code, for example ‘+91-9876543210’. A random 6-digit OTP is generated with 5 minutes

expiry time. The expiry time is in unix format. An irreversible hash of

OTP and phone numbers are generated using the ‘sha256’ algorithm. An

encrypted token of hash and expiry time is generated. API sends a status code, success message & token in response. Alongside this, the API also sends the generated OTP to a given phone number via

SMS using a third party service.

curl --location 'https://your-beneficary-api-domain.com/api/otp/send_otp' \
--header 'Content-Type: application/json' \
--data '{
    "phoneNumber" : "+91-9373607160"
}'

15. verify_otp: This API is used to verify the OTP sent to a phone

number. Token sent as response in ‘send_otp’ API, OTP sent to SMS

and phone number with country code is expected in the request body.

This token is decrypted to get hash and expiry time (in unix format). A

new hash is generated using otp and phone number. This hash is

Compared with hash decrypted from token to check similarity of OTP. Expiry time from the token is also compared with current time.

curl --location 'https://your-beneficary-api-domain.com/api/otp/verify_otp' \
--header 'Content-Type: application/json' \
--data '{
    "phoneNumber" : "+91-43983232435",
    "otp": 170549,
    "token": "3876f0e5395845bc6"
}'

CRON Jobs

1. Profile Updating CRON:- This cron job updates the values of user profile

fields based on available documents for a user. A configurable file is

maintained which consists of a list of document names which can be used

to get the value of each profile field. A folder contains JSON files. Each

file is named after a document and contains JSON paths of attributes that are correlated with profile fields. For example, ‘name’ attribute in aadhaar can be used for firstName,

lastName and middleName(fatherName), or ‘totalAnnualIncome’ from income certificate can be used for income field. This CRON job takes 10 users at a time each 5 minutes.

Users are

sorted primarily by: fieldsVerified being NULL (first),

Then false with a fieldsVerifiedAt timestamp,

Then all other cases (fieldsVerified is true or irrelevant). Within each primary group: If fieldsVerifiedAt is NULL, updated_at determines the order.

Otherwise, fieldsVerifiedAt determines the order. This CRON job also takes into consideration, the types and formats of values present in documents before updating in the database. All the major functions used in this CRON job are maintained in a separate file. These same functions are called when the documents are imported from wallet.

2. Application Status Updating CRON:- This cron job updates the status of user applications (applications to a benefit). Each user application is differentiated based on ‘external_application_id’. This attribute is also used to get the status from ONEST ‘on_status’ API (refer to document mentioned previously). This cron job takes all the applications from the database which are not yet in a final state (status which is either

‘AMOUNT RECEIVED’ or ‘REJECTED’, calls the ONEST API and updates the value. The frequency of this cron job is each 30 minutes.

Installation and Deployment

Pre-requisites

  1. UBI Network setup

  2. Nodejs ( version 16+)

  3. PostgreSQL(Version 14+)

  4. Hasura

Local Installation

  • Fork and Clone the Repository:

    • Clone the repository: GitHub Repo

    • Check out the main branch.

    • Install Dependencies:

    • Run: npm install

  • Add Environment Variables:

    • Create a .env file in the root directory with the necessary configurations from the example.env file.

  • Start the Application:

    • For development: npm run start:Dev

Deployment

Dockerfile:

A Dockerfile to containerize the application: Dockerfile

Build the Docker Image:

  1. Run: docker build -t beneficiary.

Run the Container:

  1. Run: docker run -p 3399:3399 beneficiary

GitHub Actions Deployment: Deployment file

Troubleshooting

  1. Registration Issues: Step 1: Login to keycloak. Step 2: Search for the user using firstName or lastName. Step 3: If a user exists, note down its ID in the ‘Details’ tab. Else go to step 8. Step 4: Delete the user from keycloak. Step 5: Login to pgAdmin to access the database. Step 6: Search for a user with ‘sso_id’ equal to id noted in step 3. Step 7: If a user exists, then delete the user record from the database. Note: There might be records in other tables which are bound to the user. Delete these records first with caution. Else, go to step 8. Step 8: Try to register the user again.

  2. Login Issues: Step 1: Login to keycloak. Step 2: Search for the user using firstName or lastName. Step 3: If a user exists, try to reset the password in case the password is Forgotten, and again try to log in. Else, register a new user.

  3. The issue in importing documents:

    1. Step 1: Make sure that the documents being imported are available in the wallet. Login to pgAdmin to access the wallet database and check for documents associated with the user. Step 2: If documents exist in the wallet database as well as in the beneficiary database, then delete and try to re-upload in a wallet and then import in the beneficiary app.

Step 3: Make sure that values in all documents are in valid format.

Future Enhancements

  1. Audit logs implementation

  2. RBAC + Ownership implementation

  3. More dynamicity with respect to schemas of VCs


Frontend Developer Guide:

Architecture

The Beneficiary App is a web-based application designed for seekers to apply for eligible benefits.

Seekers need their user credentials if not available need to register. After successfully logged in need to upload the required documents and explore the available benefits. Seeker can apply for benefits available in lists and get scholarships.

Framework: React (with TypeScript)

Key Responsibilities:

  • User Interface (UI) rendering.

  • Form submissions and user interaction handling

Structure:

Components:

  • Login and Registration

    • User profile

    • Upload documents from wallet

    • Explore benefits

    • See details of selected benefit

    • Apply for benefit

    • See seeker details in my application

State Management:

  • React Query for server state management (API calls, caching).

Routing: React Router for navigation (e.g., Dashboard, Benefits, Applicants).

UI Framework: Chakra UI for consistent styling and responsiveness.

Key Features

  • Authentication System

  1. Login: Secure login with username and password.

  2. Registration:

    1. Fields: First Name, Last Name, Phone Number, Password, Confirm Password.

    2. On success: Display username and redirect to the login page.

  • Beneficiary Profile

    • Document Import: Import required documents from E-Wallet.

    • Auto-Update Profile: Fetch and update profile details using fetched documents

  • Explore Benefits

    • Dynamic Listing: Display benefits based on income, caste, and vendor details.

    • Filters & Search: Search benefits by caste, income range, etc.

  • Application Process: Auto-filled application form user adds bank details and applies.

  • The application form is coming from the provider side.

  • When seekers click the 'Proceed to Apply' button, they will see the application form only if all criteria or eligibility requirements are met.

  • The form will be generated according to benefit selection. For now two benefits added one checks Sports documents and another one checks income range depending on these fields are displayed.

  • Seekers' available data from profile get prefilled and document gets selected automatically.Only seekers need to add their bank details and submit form.

  • After successful submission of form seekers get a confirmation message with order id and navigate to My Application page.

  • My Application

    • Tracking: Monitor application status (e.g., Pending, Approved).

    • Details View: View submitted application details.

4. Technical Details

  1. Frontend

    1. Framework and Libraries:

    2. React (TypeScript) type safety and code maintainability

  2. UI Framework:

    1. Chakra UI for responsive and consistent UI components

  3. Build Tool:

    1. Vite: Faster development and optimized builds for production,

  4. Programming Language:

    1. TypeScript: Ensures type safety, better code quality, and maintainability

  5. API Specification:

    1. A. RESTful APIs:

{{base_url}}/auth/register_with_password
{{base_url}}/auth/login
{{base_url}}/auth/logout
{{base_url}}/users/get_my_consents
{{base_url}}/users/consent
{{base_url}}/users/get_one/?decryptData=true
{{base_url}}/users/wallet/user_docs
{{base_url}}/users/user_applications_list
{{base_url}}/users/user_application/{id}
{{base_url}}/users/user_application
{{base_url}}/select
{{base_url}}/init
{{base_url}}/confirm
{{base_url}}/content/documents_list
{{base_url}}/content/search
  1. Environment Management:

    1. Environment variables managed via .env files

B. Tools and Integrations:

  1. DE: Visual Studio Code

    1. Linting: ESLint and Prettier for code formatting and quality

Installation and Deployment

  • Prerequisite

    • Node JS

    • NPM

    • GIT

Installation

Project Setup with Vite

Step 1: Clone the Repository

Step 1: Navigate to the Project Directory

Step 1: Create .env

Step 4: Add Environment Variables

VITE_API_BASE_URL=
VITE_API_BASE_ID=
#Keycloak-related configs
VITE_KEYCLOAK_URL=
VITE_KEYCLOAK_REALM=
VITE_KEYCLOAK_CLIENT_ID=
# Wallet related configs
VITE_EWALLET_ORIGIN=
VITE_EWALLET_IFRAME_SRC=
# Onest network related configs
VITE_BPP_URL=
VITE_BPP_ID=
VITE_BAP_URL=
VITE_PROVIDER_URL=

Step 2: Install Dependencies

Step 3: Start the Development Server-npm run dev

Step 4: Build for Production-npm run build

Last updated

Was this helpful?