B4J Tutorial Web API Server (v3)

Part #1: Get Started
Part #2: Configure Settings
Part #3: How Does the Project Work?
Part #4: Server Handlers
Part #5: Add Boilerplate Code to Handler Using Code Snippets
Part #6: Add a New Field

Introduction
This tutorial is based on [Project Template] Web API Server 3.

Installation
  1. Download and save Web API Server (3.00).b4xtemplate file into B4J Additional Libraries folder.
  2. You also need to download WebApiUtils.b4xlib (v3.02) and save inside the same folder.
  3. It is recommended to use this template with MiniORMUtils.b4xlib (v1.14) which is a dependency by default.
    You can put this library inside B4X Additional Libraries folder since it is a cross platform library.
    Note: You can also use MinimaListUtils library or write your own SQL code using jSQL library.
  4. By default the template is based on SQLite database as backend.
    Therefore you need sqlite-jdbc driver in your B4J or B4X Additional Libraries folder. B4J comes with sqlite-jdbc-3.7.2 as Internal Library.
  5. If you want to use MySQL database, you need to use MySQL jdbc driver and put inside B4J Additional Libraries folder. e.g mysql-connector-java-5.1.49
    B4J Additional Libraries
Create Project
  1. Once all the required libraries and template are in place, you can start B4J IDE.
  2. Select from File menu, New, Web API Server (3.x)
    Select the project template
  3. Enter the name of your project as you like. Then click OK.
    New Project
  4. Now the project is ready to run as it is.
Running the Project
  1. Click menu Project, Compile & Run (F5) or the play button on the toolbar.
  2. Hover you mouse pointer to the AppStart sub and click on the highlighted link to open the app on your web browser.
    Open in browser
  3. The browser will open and load the index page.
    It is a CRUD web application where you can add a new product (C), search for a product (R), edit an existing product (U) and delete a product (D).
  4. It has already generated some common APIs following the RESTful API principal.
    To see the list of APIs, click the API link with a gear icon on top navigation bar.
    Click the API link
  5. You will be redirected to the API documentation page.
    You will see there are endpoints such as GET, POST, PUT and DELETE which are represented in different colours.
    Click to expand the section
  6. You can click on any item to expand the section.
Perform Tests

GET
  1. Let's try to perform our first GET request with an id parameter.
    Click on the second end point labeled GET /api/categories/:id
  2. Edit the Path by replacing the :id parameter with 2 and click on the green Submit button.
    Edit the Path
  3. The form will send an AJAX request to the API server and return a JSON response (Code 200 for Success).
    JSON Response Success.png
POST
  1. To test a POST end point, click the third item with labeled POST /api/categories.
    Copy the sample format from the left and paste in the Body textarea.
    Make POST request
  2. Edit the content, i.e value of the category name in the illustration and click the yellow Submit button.
    JSON Response Created.png
  3. You may follow the convention to return code 201 Created for new item added to the resource.
    For this demo, it is set to return a simple response and it doesn't support text Created but display success as default text.
    You need to set Simple Response to False in server configuration to return code 201 response for your client apps.
This is the end of tutorial for now. Thanks for taking your time to read it.

I will update this tutorial again from time to time.

Please do not post any question on this thread.

Always start a new question with the name Web API Server 3 or post your question here : [Q&A] Web Api Server 3
 
Last edited:

aeric

Expert
Licensed User
Longtime User
Part #2: Configure Settings
  1. B4J generate a config.ini when you compile the project for the first time in Debug (or Release) mode.
  2. You can open the file using Notepad or any Text Editor.
  3. After you made any changes, you need to compile the project again for the settings to take effect.
B4X:
# Define App Constants
HOME_TITLE=WEB API SERVER
APP_TITLE=Web API Server
APP_TRADEMARK=B4X
APP_COPYRIGHT=Copyright Computerise System Solutions 2024
  1. You can edit these settings.
  2. It will applies to the text displayed on the front-end and API documentation page.
  3. Similar to config.properties, the values does not support UTF8. You may want to set text for non-English characters in the code.
B4X:
# Server Path
ROOT_URL=http://127.0.0.1
ROOT_PATH=
API_NAME=api
API_VERSIONING=False
  1. ROOT_URL (default=http://127.0.0.1) is for setting the server URL. You can edit to your domain name in production release.
    Note: It seems Chrome is no longer allowed http://localhost so it is better to stick with http://127.0.0.1 during development.
  2. ROOT_PATH (default=<empty>) can be set to other name e.g /web
  3. API_NAME (default=api) is better to leave as default. You need to change the values in code, html templates and JavaScript files if you edit it.
  4. API_VERSIONING (default=False) can be set to e.g /v1 or /preprod
B4X:
# Java server port
ServerPort=8080
SSLPort=0
  1. It is important to set the correct number for B4J server port.
  2. Make sure the port number is not used by another service or blocked by the firewall.
  3. You can set the port e.g 80
  4. It is recommended to use SSL protocol in production. If SSLPort number is set to 0, SSL is disabled.
  5. You can set it to e.g 443
B4X:
# SSL Keystores
# Generate a keystore from Windows CMD
# C:\Java\jdk-11.0.1\bin\keytool -keystore keystore -alias jetty -genkey -keyalg RSA
# Copy keystore file to Objects folder of the B4J project
SSL_KEYSTORE_DIR=/etc/letsencrypt/live/mydomain.com
SSL_KEYSTORE_FILE=keystore
SSL_KEYSTORE_PASSWORD=xxxxxxxxx
  1. If SSLPort is not set to 0 (enabled), you need to set the keystore settings.
  2. During development, you can use CMD to call keytool in the JDK to generate a self sign keystore file.
  3. Put this file in Objects folder and leave the SSL_KEYSTORE_DIR setting as <empty>.
  4. During production, you can use a real certificate or generate one using LetsEncrypt.
B4X:
# DATABASE CONFIGURATION

## SQLite configuration:
DbType=SQLite
DbFile=webapi3.db
DbDir=
DriverClass=org.sqlite.JDBC
JdbcUrl=jdbc:sqlite:{DbDir}/{DbFile}
  1. SQLite is set as default DbType.
  2. You can change the DbFile as you like e.g server.sqlite
  3. You can leave DbDir as <empty> to store the file inside Objects directory.
  4. If you want to save the file in a different location, you need to set it using forward slash or escape character e.g C:/server/db or C:\\server\\db
  5. It is recommended to leave the values for DbType, DriverClass and JdbcUrl as default.
  6. If you want to use MySQL as backend database, uncomment the settings like the following example.
    B4X:
    ## MySQL configuration:
    DbType=MySQL
    DbName=webapi3
    DbHost=localhost
    DbPort=
    DriverClass=com.mysql.cj.jdbc.Driver
    JdbcUrl=jdbc:mysql://{DbHost}:{DbPort}/{DbName}?characterEncoding=utf8&useSSL=false
    User=root
    Password=password
    MaxPoolSize=100
  7. You can leave DbPort as <empty> if MySQL is running on default port 3306.
  8. You can edit the DbName, User, Password, MaxPoolSize or other settings according to your needs.
This is the end of tutorial for Part #2. Thanks for taking your time to read it.

Always start a new question with the name Web API Server 3 if you have any question on this tutorial.
 
Last edited:

aeric

Expert
Licensed User
Longtime User
Part #3: How Does the Project Work?
  1. First thing to know, this is a B4J server app. Therefore, it depends on the B4J Server library.
  2. You also need to understand, it is a non-UI or console app. It doesn't have native UI library or component.
  3. You can't add any B4J library that depends on XUI or jFX.
  4. After the project is compiled as Release, you can run the binary file on Windows CMD or Terminal on Linux.
  5. To host the server app, it is recommended to find a VPS that you have full access to run the binary using a remote SSH terminal.
  6. You also need to have a JDK on the server for you to call the .jar file.
  7. It is recommended that you put the compiled binary file e.g server.jar, together with the www directory inside a location that you have write permission.
  8. The server will have to write it's access log and database file if you are using SQLite to keep inside the location same as the binary file.
  9. You also need to make sure the server can read the config.ini file and contents inside www directory.
  10. You can follow other tutorials about running B4J Server on this forum.
Starting the Server
  1. To start the server, you Initialize the object as global in Main module and call the method Start.
  2. We should configure the settings before we start the server.
  3. The default server port number for B4J server is 8080.
  4. We can change this setting using InitServer sub in the Main module.
Initializing Server Configuration
  1. In this project, we use config.ini to make changes to the server configuration without needing to recompile the project.
  2. The settings in config.ini has been explained in the previous tutorial.
  3. This project has embedded with a config.example file in the assets or Files directory.
  4. If you have deleted the config.ini file, a new config.ini file will be copied from the example template.
  5. InitServer sub will read config.ini file for the configuration settings.
  6. All the configuration will be assigned to a Map object name ctx where it can be read from anywhere of the project and also use by HTML template engine, such as for displaying the title label and version number.
EnableCORS​
  1. This setting must be enabled to allow another application (from different origin) to access our API especially when using JavaScript.
  2. If you have another web app hosted with different port number e.g Vue app running on port 3000 and this server is running on port 8000 then you need to enable CORS or otherwise you will get errors in web browser.
  3. You can disable this setting if you are sure there are no other application sharing the API.
  4. This is not applied to mobile or desktop native client apps want to access the API.
EnableSSL​
  1. This setting is recommended to be set to True during production to protect the data transfer being encrypted between the server and client.
  2. As explained in previous tutorial, you need to configure the SSLPort to number other than 0 and provide information of the keystore.
EnableHelp​
  1. Basically this setting is use to hide the API documentation link to be visible to the end user.
  2. You can also use Conditional symbol to add or disable the HelpHandler during production.
SimpleResponse​
  1. This setting allows you to choose the API output in different JSON format.
  2. By default, it is not enabled.
  3. This means the output of the JSON Response follows a predefined structure as set inside ReturnHttpResponse sub of WebApiUtils library.
  4. Learn more about SimpleResponse in next tutorial.
StaticFilesFolder​
  1. Usually the static files folder is set as www inside Objects during development.
  2. It is recommended to leave the folder name as www or you may want to change as public.
  3. After compiled as Release, it will become the same level as the jar file.
StaticFilesBrowsable​
  1. You may want to restrict the client to list the contents of your www assets directory from the browser.
  2. You can set StaticFilesBrowsable to False which is by default also set as False.
    B4X:
    Config.StaticFilesFolder = File.Combine(File.DirApp, "www")
    Config.StaticFilesBrowsable = False
Version​
  1. The VERSION_NAME is set in Process_Globals which is just for displaying purpose in front end and terminal log as "welcome message".
    B4X:
    Config.Version = VERSION_NAME
Applying Server Configuration
  1. The ApplySettings sub will take care of applying the settings if you have override any inside InitServer sub.
  2. You must not modify any code inside ApplySettings sub.
Initializing Database Configuration
  1. The InitDatabase sub will take care of applying the settings read from the config.ini file.
  2. You must not modify any code inside InitDatabase sub.
Connecting to the Database
  1. Based on the Build Configuration that you selected, the project is connecting to SQLite by default.
  2. You need to select MySQL from the dropdown list if you want to use MySQL database.
  3. CreateConnection sub will take care of applying the settings and attempt to connect to the database.
  4. It will also check if database is exist or else call CreateDatabase sub.
Creating the Demo Database
  1. If no database is present, the CreateDatabase sub will create a new database, generate the tables and insert some dummy data.
  2. As this template is using MiniORMUtils library, the same code is use to accomplish this task, regardless of whether you choose SQLite or MySQL.
  3. The SQL queries are handled internally by the library despite the differences between the 2 databases.
  4. You may want to learn about using MiniORMUtils in another tutorial or post a new question on this library.
Configure Cross Origin (CORS)
  1. If you have set Config.EnableCORS = True, then you may want to configure the Cross Origin settings on this server accessed from another origin or server inside the ConfigureCORS sub.
  2. This is the case when you have another application that is using JavaScripts to make API calls to this server.
  3. By default, there is already an example added for you.
    B4X:
    Paths.Add(CreateMap("path": "/api/*", "origins": "http://localhost, http://127.0.0.1:3000", "methods": "POST,PUT,DELETE", "headers": "*"))
  4. This mean you are allowing another application specified in the list of "origins" to access our /api "path" and its derivatives using the "methods" POST, PUT and DELETE.
Configure SSL for the Server
  1. The ConfigureSSL sub will take care of setting up the SSL. You must not change any code in this sub.
  2. This sub will display some messages in the Logs depending on the settings for EnableSSL and the settings in config.ini file.
Configure Static Files
  1. The ConfigureStaticFiles sub will take care of setting up the directory name and browse permission of static file folder. You must not change any code in this sub.
Switching of Database
  1. If you have decided to switch to another database by selecting a different item from the Build Configurations dropdown list, make sure you have commented the SQLite configuration explained in the previous tutorial Part #2.
  2. If the configuration does not match, the ShowBuildConfigurationNotMatch sub will display a warning message and the application will be terminated immediately.
Commonly Use Subs
  1. You can add extra subs to the Main module so they are reusable in any of the server handler classes.
  2. There are 3 subs added for you for communicating with the database.
  3. Note that DBConnector is part of the MiniORMUtils library.
    B4X:
    Public Sub DBEngine As String
        Return DBConnector.DBEngine
    End Sub
    
    Public Sub DBOpen As SQL
        Return DBConnector.DBOpen
    End Sub
    
    Public Sub DBClose
        DBConnector.DBClose
    End Sub
This is the end of tutorial for Part #3. Thanks for taking your time to read it.

Always start a new question with the name Web API Server 3 if you have any question on this tutorial.
 
Last edited:

aeric

Expert
Licensed User
Longtime User
Part #4: Server Handlers

There are 4 types of Class for writing code in B4J.
  • Standard Class
  • Server Filter
  • Server Handler
  • Server WebSocket
In this tutorial, we will focus on Server Handler.
This is the class that we use most of the time to write all of the business logic for the API.

Conventions
  1. For this Web API Server template, we use the following naming convention to differentiate the purpose of the handler class.
    • Api Handler - to read the Request from the URI contains the path /api and output the Response as JSON
    • Web Handler - to read the Request from the URI without the /api path and output the Response as HTML
  2. For e.g there are 2 handlers for Categories i.e CategoriesApiHandler and CategoriesWebHandler.
    Handlers for Categories
  3. You can either have Api handler and/or Web handler.
  4. As you can guess, CategoriesApiHandler is use for taking care of the API endpoints for Categories while CategoriesWebHandler is use to display the web page to manage the Categories.
  5. IndexWebHandler is use to display the index page when a user browse to the root path.
  6. The index page calls the API endpoints from ProductsApiHandler and FindApiHandler.
  7. HelpHandler is use to display the API documentation page. It will be use to call any API endpoint that you have created.
  8. Each Web handler is linked to its respective JavaScript file to make AJAX calls and process the returned JSON Response from an Api handler.
Adding Server Handler
  1. There are 2 ways to add a Server Handler.
  2. We can click on the menu Project, Add New Module, Class Module,
    Click on the menu Project
  3. However, I recommend another way. Right click on the Handlers group in Modules tab and select Server Handler.
    By this way, we don't need to drag the class into the group later.
    Right click on Modules group
  4. Enter the name of the handler and click Ok.
    1730526607349.png
  5. An empty class with a Handle sub is generated.
    B4X:
    'Handler class
    Sub Class_Globals
       
    End Sub
    
    Public Sub Initialize
       
    End Sub
    
    Sub Handle(req As ServletRequest, resp As ServletResponse)
       
    End Sub
  6. Please continue to read tutorial Part #6 on how to use Code Snippets to add boilerplate code for the handler.

This is the end of tutorial for Part #4. Thanks for taking your time to read it.

Always start a new question with the name Web API Server 3 if you have any question on this tutorial.
 
Last edited:

aeric

Expert
Licensed User
Longtime User
About SimpleResponse
This tutorial is extended from Part #3 Initializing Server Configuration.

SimpleResponse.Enable = False
  1. By default, a predefined structure is return when using ReturnHttpResponse sub of WebApiUtils to output the JSON response.
  2. I recommend to use the default because it covers the response status code, message and error message without the need to worry whether the response should be an array or object. The response data is always returned as an array (list).
    Here is an example of the output:
    JSON:
    {
      "a": 200,
      "r": [
        {
          "deleted_date": null,
          "category_id": 2,
          "id": 3,
          "product_price": 1000,
          "created_date": "2024-10-30 08:33:13",
          "product_code": "T002",
          "modified_date": null,
          "product_name": "Optimus Prime"
        }
      ],
      "s": "ok",
      "e": null,
      "m": "Success"
    }
  3. You won't notice this format in API documentation because only the response 'r' item is show. A small label is display below the response box results from the combination of 'a' and 'm' / 'e' items.
  4. For details, you can check the JavaScript code of help.js file inside scripts folder of www/assets.
    JavaScript:
    success: function (data) {
        if (data.s == "ok" || data.s == "success") {
            var content = JSON.stringify(data.r, undefined, 2)
            $("#response" + id).val(content)
            $("#alert" + id).html(data.a + ' ' + data.m)
            $("#alert" + id).removeClass("alert-danger")
            $("#alert" + id).addClass("alert-success")
            $("#alert" + id).fadeIn()
        }
        else {
            var content = JSON.stringify(data.r, undefined, 2)
            $("#response" + id).val(content)
            $("#alert" + id).html(data.a + ' ' + data.e)
            $("#alert" + id).removeClass("alert-success")
            $("#alert" + id).addClass("alert-danger")
            $("#alert" + id).fadeIn()
        }
    }
SimpleResponse.Enable = True
  1. You will notice in this version, the setting is set to True inside the code.
    B4X:
    Config.SimpleResponse.Enable = True
    Note: You can comment the code if you want.
  2. This is the case where you want to create API that follow the structure you want.
  3. When this setting is set to True, the JSON format as showed above is now show with just the 'r' part, exactly as what you see in the Response text area in API documentation page.
    JSON:
    {
        "deleted_date": null,
        "category_id": 2,
        "id": 3,
        "product_price": 1000,
        "created_date": "2024-10-30 08:33:13",
        "product_code": "T002",
        "modified_date": null,
        "product_name": "Optimus Prime"
    }
  4. You can actually compare the difference by viewing the actual output by pasting the API end point to the browser address.
    Take note that you can only see the output for GET request in browser.
    Using browser
Debugging using Developer Tools
  1. We can use the Developer Tool in web browser for debugging the API response.
  2. First we need to write console.log() inside the JavaScript for AJAX call.
  3. This is useful for debugging other methods such as POST, PUT and DELETE.
  4. When Config.SimpleResponse.Enable = True, the response is in simple format.
  5. The page for API documentation will load help.simple.js file to make AJAX calls.
  6. Open help.simple.js using a Text Editor and add the following code at line #88.
    console.log(data)
  7. The code will look like the following:
    JavaScript:
    case element.hasClass("get"):
        return {
            type: "GET",
            headers: headers,
            success: function (data, textStatus, xhr) {
                console.log(data)
                var content = JSON.stringify(data, undefined, 2)
                $("#response" + id).val(content)
                $("#alert" + id).html(xhr.status + ' ' + textStatus)
                $("#alert" + id).removeClass("alert-danger")
                $("#alert" + id).addClass("alert-success")
                $("#alert" + id).fadeIn()
            },
            error: function (xhr, textStatus, errorThrown) {
                var content = xhr.responseText
                $("#response" + id).val(content)
                $("#alert" + id).html(xhr.status + ' ' + errorThrown)
                $("#alert" + id).removeClass("alert-success")
                $("#alert" + id).addClass("alert-danger")
                $("#alert" + id).fadeIn()
            }
        }
        break
  8. After changes is saved, you need to refresh the JavaScript source by right clicking on the page and choose view page source (Ctrl+U).
    Scroll down to the bottom of the page until you see
    <script src="http://127.0.0.1:8080/assets/scripts/help.simple.js"></script>
  9. Click on the link to open it on a new tab and right-click Reload (Ctrl+R).
  10. Open the Dev Tools by right clicking on the page (on Google Chrome) and choose Inspect (usually you need to scroll to bottom of the pop up menu) or just press combo keys of Ctrl+Shift+I.
  11. The Dev Tools panel will appear. Usually it is focusing on the Console tab. Otherwise you need to click on the Console tab.
  12. Now try to make a new GET request.
    You should see an arrow with text Object appeared and on the left showing 1 user message.
    On the right you see the name of the JavaScript file colon the line number on the code that produce the message log.
    Web Developer Tools
  13. Click the Object to expand it. You can see the content of the JSON object return from the API.
    Show Object
  14. If it is not working, probably you need to restart the server. Stop the debug and click run again. Then try again.
  15. The default JSON Format for SimpleResponse is "Auto".
    You can change the Format by setting it to "Map" to return as object (or "List" to return as array) for all the API responses.
    SimpleResponse.Format
  16. By changing it to Map, you need to specify the key of the "Map" using the DataKey property.
    If you don't specify a key name, it will use "data" as the default key.
    B4X:
    Config.SimpleResponse.Initialize
    Config.SimpleResponse.Enable = True
    Config.SimpleResponse.Format = "Map"
    Config.SimpleResponse.DataKey = "result"
  17. After made this changes, now try to restart the debug (F11) and see the difference of output but this time we test on the API end point that should return an array (list) which is the /api/products end point.
    SimpleResponse.Format = Map
  18. So you see the response is now return as object (wrapped in curly brackets) instead as an array (wrapped in square brackets) and it is using the key that we specified.
Conclusion
  1. There are 4 types of output for SimpleResponse:
    EnableFormatReturn data
    False(Fixed)object with r data always an array
    TrueAuto (default)depends whether we initialize object or data
    TrueMapalways as object (with optional customize key name)
    TrueListalways as array
  2. It depends on your use case which type is suitable.
  3. You can build more complex JSON response by nesting the objects and arrays.
This is the end of tutorial explaining about SimpleResponse. Thanks for taking your time to read it.

Always start a new question with the name Web API Server 3 if you have any question on this tutorial.
 
Last edited:

aeric

Expert
Licensed User
Longtime User
Part #5: Add Boilerplate Code to Handler Using Code Snippets
  1. We have learned in Part #4 that we need to write our code inside Server Handlers.
  2. We can save time by generating some boilerplate code using Code Snippets included inside WebApiUtils.
  3. We continue to work with UsersApiHandler.
  4. Let's delete all the code but left the Class_Globals sub.
    1730527427688.png
    Note: We can also use a Standard class.
  5. Put your cursor inside Class_Globals sub, now start typing "globals".
    Select the Code Snippet using Up/Down key
    Select the "Api Class Globals" Code Snippet using the Up/Down key and press Enter.
  6. The result is the following:
    B4X:
    Sub Class_Globals
            Private Request As ServletRequest
            Private Response As ServletResponse
            Private HRM As HttpResponseMessage
            Private DB As MiniORM
            Private Method As String
            Private Elements() As String
            Private ElementId As Int
    End Sub
  7. The variable names are quite self explanatory. We will be using HttpResponseMessage from WebApiUtils and MiniORM class.
  8. Put the cursor after the Class_Globals sub and start typing "handler".
    Use the Up/Down key to select "Api Handler" code snippet then press Enter.
    Api Handler Code Snippet
  9. The default code is added. You will notice there are some texts are highlighted.
    Api handler boilerplate code
  10. Replace "EndPoints" with "Users" for plural word and "EndPoint" with "User" for singular word.
    Replace the highlighted text
  11. Press Tab key to switch to next occurrences if they exist.
    Here, w
    e also need to change the "TableName" to the name of your users table.
    Replace TableName
  12. When the cursor returned to the first highlighted text, we can press Enter to accept the changes.
  13. Now the handler is ready. You can make further customization to this handler.
  14. Take note that we still need to create the users table and add the handler to the server.
    Main:
    Sub AppStart (Args() As String)    srvr.Initialize("")
        srvr.AddHandler("", "IndexWebHandler", False)                           ' Home handler
        srvr.AddHandler("/categories/*", "CategoriesWebHandler", False)         ' Web handler
        srvr.AddHandler("/api/categories/*", "CategoriesApiHandler", False)     ' API handler
        srvr.AddHandler("/api/products/*", "ProductsApiHandler", False)         ' API handler
        srvr.AddHandler("/api/users/*", "UsersApiHandler", False)               ' API handler
        srvr.AddHandler("/api/find/*", "FindApiHandler", False)                 ' API handler
        srvr.AddHandler("/help", "HelpHandler", False)                          ' Help handler
  15. To add the endpoints to API documentation, add the code inside ReadHandlers sub.
    HelpHandler:
    Public Sub ReadHandlers (FileDir As String) As String
        Dim strHtml As String
        Log(TAB)
        Log("Generating Help page ...")
        Dim verbs(4) As String = Array As String("GET", "POST", "PUT", "DELETE")
        Dim DocumentedHandlers As List
        DocumentedHandlers.Initialize
        Dim Handlers As List
        Handlers.Initialize
        Handlers.Add("CategoriesApiHandler")
        Handlers.Add("ProductsApiHandler")
        Handlers.Add("UsersApiHandler")
        Handlers.Add("FindApiHandler")
  16. The steps are similar for adding code snippets for Web handler.
This is the end of tutorial for Part #5. Thanks for taking your time to read it.

Always start a new question with the name Web API Server 3 if you have any question on this tutorial.
 
Last edited:

aeric

Expert
Licensed User
Longtime User
Next, check on the tutorial on using MiniORMUtils.
 

aeric

Expert
Licensed User
Longtime User
Part #6: Add a New Field
  1. This is a tutorial copied from the conversation between a member and I in a private message.
  2. I would like to share this for benefit of other members.
  3. This is a guide to add an Integer field for "opening stock".
  4. In this example, we make it compulsory to supply the value during Add new product in index page.
Modify Product Table
  1. Go to CreateDatabase sub in Main module.
  2. We need to add the following code
    B4X:
    MDB.Columns.Add(MDB.CreateORMColumn2(CreateMap("Name": "opening_stock", "Type": MDB.INTEGER, "Default": "0")))
  3. Here we are adding a column of type integer and default the value to 0.
  4. Now the code will look like the following:
    B4X:
    MDB.Table = "tbl_products"
    MDB.Columns.Add(MDB.CreateORMColumn2(CreateMap("Name": "category_id", "Type": MDB.INTEGER)))
    MDB.Columns.Add(MDB.CreateORMColumn2(CreateMap("Name": "product_code", "Length": "12")))
    MDB.Columns.Add(MDB.CreateORMColumn2(CreateMap("Name": "product_name")))
    MDB.Columns.Add(MDB.CreateORMColumn2(CreateMap("Name": "product_price", "Type": MDB.DECIMAL, "Length": "10,2", "Default": "0.00")))
    MDB.Columns.Add(MDB.CreateORMColumn2(CreateMap("Name": "opening_stock", "Type": MDB.INTEGER, "Default": "0")))
    MDB.Foreign("category_id", "id", "tbl_categories", "", "")
    MDB.Create
  5. Next, we open ProductsApiHandler class to modify the code for POST and PUT endpoints.
  6. In PostProduct sub we update the #Body comment to accept a new item with key "stock".
    B4X:
    ' #Body = {<br>&nbsp;"cat_id": category_id,<br>&nbsp;"code": "product_code",<br>&nbsp;"name": "product_name",<br>&nbsp;"price": product_price,<br>&nbsp;"stock": 0<br>}
  7. This item will be map to our column "opening_stock".
    add stock item for body format
  8. We add a code to check the "stock" key in request body or json payload and replace it with the correct table column.
    B4X:
    If data.ContainsKey("stock") Then
        data.Put("opening_stock", data.Get("stock"))
        data.Remove("stock")
    End If
    checking for stock key
  9. Add the column as required keys.
    B4X:
    ' Check whether required keys are provided
    Dim RequiredKeys As List = Array As String("category_id", "product_code", "product_name", "opening_stock") ' "product_price" is optional
    For Each requiredkey As String In RequiredKeys
        If data.ContainsKey(requiredkey) = False Then
            HRM.ResponseCode = 400
            HRM.ResponseError = $"Key '${requiredkey}' not found"$
            ReturnApiResponse
            Return
        End If
    Next
    1731058681872.png
  10. Add new column to the Columns array of MiniORM.
    B4X:
    ' Insert new row
    DB.Reset
    DB.Columns = Array("category_id", _
    "product_code", _
    "product_name", _
    "product_price", _
    "opening_stock", _
    "created_date")
    DB.Parameters = Array(data.Get("category_id"), _
    data.Get("product_code"), _
    data.Get("product_name"), _
    data.GetDefault("product_price", 0), _
    data.GetDefault("opening_stock", 0), _
    data.GetDefault("created_date", WebApiUtils.CurrentDateTime))
    DB.Save
  11. Similarly in PutProductById sub. The completed code should look like the following.
    B4X:
    Private Sub PutProductById (Id As Int)
        ' #Desc = Update Product by id
        ' #Body = {<br>&nbsp;"cat_id": category_id,<br>&nbsp;"code": "product_code",<br>&nbsp;"name": "product_name",<br>&nbsp;"price": product_price,<br>&nbsp;"stock": 0<br>}
        ' #Elements = [":id"]
        Dim data As Map = WebApiUtils.RequestData(Request)
        If Not(data.IsInitialized) Then
            HRM.ResponseCode = 400
            HRM.ResponseError = "Invalid json object"
            ReturnApiResponse
            Return
        End If
    
        ' Deprecated: Make it compatible with Web API Client v1 (will be removed)
        If data.ContainsKey("cat_id") Then
            data.Put("category_id", data.Get("cat_id"))
            data.Remove("cat_id")
        End If
        If data.ContainsKey("code") Then
            data.Put("product_code", data.Get("code"))
            data.Remove("code")
        End If
        If data.ContainsKey("name") Then
            data.Put("product_name", data.Get("name"))
            data.Remove("name")
        End If
        If data.ContainsKey("price") Then
            data.Put("product_price", data.Get("price"))
            data.Remove("price")
        End If
        If data.ContainsKey("stock") Then
            data.Put("opening_stock", data.Get("stock"))
            data.Remove("stock")
        End If
      
        ' Check conflict product code
        DB.Table = "tbl_products"
        DB.Where = Array("product_code = ?", "id <> ?")
        DB.Parameters = Array As String(data.Get("product_code"), Id)
        DB.Query
        If DB.Found Then
            HRM.ResponseCode = 409
            HRM.ResponseError = "Product Code already exist"
            ReturnApiResponse
            DB.Close
            Return
        End If
      
        DB.Find(Id)
        If DB.Found = False Then
            HRM.ResponseCode = 404
            HRM.ResponseError = "Product not found"
            ReturnApiResponse
            DB.Close
            Return
        End If
    
        DB.Reset
        DB.Columns = Array("category_id", _
        "product_code", _
        "product_name", _
        "product_price", _
        "opening_stock", _
        "modified_date")
        DB.Parameters = Array(data.Get("category_id"), _
        data.Get("product_code"), _
        data.Get("product_name"), _
        data.GetDefault("product_price", 0), _
        data.GetDefault("opening_stock", 0), _
        data.GetDefault("modified_date", WebApiUtils.CurrentDateTime))
        DB.Id = Id
        DB.Save
    
        HRM.ResponseCode = 200
        HRM.ResponseMessage = "Product updated successfully"
        HRM.ResponseObject = DB.First
        ReturnApiResponse
        DB.Close
    End Sub
  12. Backend API is done. Now we go to front end.
Modify the Front End
  1. Open the Files folder of the project with a text editor.
  2. Open the index.html file.
  3. We will focus on the modal form for "new" and "edit".
    1731059142200.png
  4. We can copy the code for price and replace the word "price" to "stock".
    1731059411671.png
  5. We also copy this code for "edit" but we need to change the id to "stock1" because the id must be unique.
    1731059799699.png
  6. Now we move on to the JavaScript part.
  7. Because this template supports 3 types of JSON output as set in SimpleResponse, 3 different JS files are used.
    If you are use the default, the server will load the search.simple.js file when loading index.html page.
    1731060171608.png
  8. The code for jQuery Validate is currently checking for product code and product name which are set as compulsory.
  9. Category id is not checked because it is from drop down list, user must select an item.
  10. Product Price also not checked because we let it as not compulsory. If user did not enter anything to input box then the value is default to 0.
  11. Now we will add a code to validate user input to enter "opening stock".
    1731060546157.png
  12. This step is optional if you don't want to set it as compulsory or required field.
  13. We will leave the #update part as not compulsory for "opening stock" field.
  14. Next, we write the code for displaying the search result table.
  15. Find the code for #btnsearch click.
    1731060760273.png
  16. We need to add a new column "Stock" after "Price" to the html table header.
    B4X:
    tbl_head = "<thead class=\"bg-light\"><th style=\"text-align: right; width: 60px\">#</th><th>Code</th><th>Category</th><th>Name</th><th style=\"text-align: right\">Price</th><th style=\"text-align: right\">Stock</th><th style=\"width: 90px\">Actions</th></thead>"
  17. Then we add new variables for the column and value.
    1731060957022.png
  18. Add the following code to assign the value to table cell and temporary variable stock.
    1731061023504.png
  19. Add the temporary variable for stock to col_edit so it can pass the data to the modal form when edit button is clicked.
    JavaScript:
    col_edit = "<td><a href=\"#edit\" class=\"text-primary mx-2\" data-toggle=\"modal\"><i class=\"edit fa fa-pen\" data-toggle=\"tooltip\" data-id=\"" + id + "\" data-code=\"" + code + "\" data-category=\"" + catid + "\" data-name=\"" + name + "\" data-price=\"" + price + "\" data-stock=\"" + stock + "\" title=\"Edit\"></i></a> <a href=\"#delete\" class=\"text-danger mx-2\" data-toggle=\"modal\"><i class=\"delete fa fa-trash\" data-toggle=\"tooltip\" data-id=\"" + id + "\" data-code=\"" + code + "\" data-category=\"" + catid + "\" data-name=\"" + name + "\" title=\"Delete\"></i></a></td>"
    1731061279508.png
  20. Then add col_stock to tbl_body.
    JavaScript:
    tbl_body += "<tr>" + col_id + col_code + col_category + col_name + col_price + col_stock + col_edit + "</tr>"
    1731061383297.png
  21. We also need to update the click event for the inline edit button.
    This is to load the value from result table into the edit modal form.
    1731061533637.png
  22. The search button code is done but there is a function to load the table when the page loads without clicking on button search.
  23. There is a function using GET request to get all the rows by default using $.getJSON("/api/find") ajax call.
    1731061695161.png
  24. This part will be almost the same for the #btnSearch.
  25. The updated script will look like the following.
    JavaScript:
      $.getJSON("/api/find", function (response) {
        var tbl_head = ""
        var tbl_body = ""
        if (response.length) {
          tbl_head = "<thead class=\"bg-light\"><th style=\"text-align: right; width: 60px\">#</th><th>Code</th><th>Category</th><th>Name</th><th style=\"text-align: right\">Price</th><th style=\"text-align: right\">Stock</th><th style=\"width: 90px\">Actions</th></thead>"
          tbl_body += "<tbody>"
          $.each(response, function () {
            var col_id = ""
            var col_code = ""
            var col_category = ""
            var col_name = ""
            var col_price = ""
            var col_stock = ""
            var col_edit = ""
            var id
            var code
            var name
            var price
            var stock
            var catid
            $.each(this, function (key, value) {
              if (key == "id") {
                col_id = "<td class=\"align-middle\" style=\"text-align: right\">" + value + "</td>"
                id = value
              }
              else if (key == "product_code") {
                col_code = "<td class=\"align-middle\">" + value + "</td>"
                code = value
              }
              else if (key == "category_name") {
                col_category = "<td class=\"align-middle\">" + value + "</td>"
              }
              else if (key == "product_name") {
                col_name = "<td class=\"align-middle\">" + value + "</td>"
                name = value
              }
              else if (key == "product_price") {
                col_price = "<td class=\"align-middle\" style=\"text-align: right\">" + value + "</td>"
                price = value
              }
              else if (key == "opening_stock") {
                col_stock = "<td class=\"align-middle\" style=\"text-align: right\">" + value + "</td>"
                stock = value
              }           
              else if (key == "category_id") {
                catid = value
              }
            })
            col_edit = "<td><a href=\"#edit\" class=\"text-primary mx-2\" data-toggle=\"modal\"><i class=\"edit fa fa-pen\" data-toggle=\"tooltip\" data-id=\"" + id + "\" data-code=\"" + code + "\" data-category=\"" + catid + "\" data-name=\"" + name + "\" data-price=\"" + price + "\" data-stock=\"" + stock + "\" title=\"Edit\"></i></a> <a href=\"#delete\" class=\"text-danger mx-2\" data-toggle=\"modal\"><i class=\"delete fa fa-trash\" data-toggle=\"tooltip\" data-id=\"" + id + "\" data-code=\"" + code + "\" data-category=\"" + catid + "\" data-name=\"" + name + "\" title=\"Delete\"></i></a></td>"
            tbl_body += "<tr>" + col_id + col_code + col_category + col_name + col_price + col_stock + col_edit + "</tr>"
          })
          tbl_body += "</tbody>"
        }
        else {
          tbl_body = "<tr><td>No results</td></tr>"
        }
        $("#results table").html(tbl_head + tbl_body)
      })
    })
  26. That's all now. Delete the sqlite database file and run the project again in debug.

    1731061979835.png
 
Top