Pre-requisites
The Web API
For our web API to be useful, it should allow us to do the following:
- Add a user
- Get a list of users
- Send a message to a user
- Get a list of messages for a user
- Read a message
We will add a method for each of these to our web API.
Business Rules
A few rules will help our messaging app implementation make more sense:
- Each user will be uniquely defined by his/her email address. No two users may have the same email address. We call this the natural key for the user. Each user will also have a surrogate key in the database. In MongoDB, the surrogate key field is a special field named _id. The reasons why this is a good practice are beyond the scope of this sample. It might be helpful to Google it at some point if you don’t know.
- Each message is for one and only one user. The message must therefore contain a field for the associated user’s _id. A message has a surrogate key as for a user, but there is no logical natural key for a message.
Conventions
- We will use underscores in names in Linx, wherever we would otherwise want to use spaces because Linx does not play nicely with spaces in names.
- The property values for some functions in Linx may contain expressions. In the value input box of such a property, an expression is denoted with a prepended “=” (equals sign). Throughout this sample, expressions like these will be provided with the = prefix included, for ease of pasting directly into the property value input box. If such an expression is inserted into the expression editor, the = prefix should be omitted.
Create the Linx Solution for the Messaging App
Open the Linx designer, and create a new project. Name the solution Messaging_App, and name the default project Server. Delete the default Process from the Server project.
Messaging App Dependencies
We will need the following Linx plugins (from the toolbox on the right) to implement our messaging server:
- REST, for the web service which will host our web API.
- Database, for the MongoDB tools for reading and writing data.
Messaging App Settings
In the course of implementing our messaging app, we will refer repeatedly to things like the database connection, and the base URL of our web API server. What’s more, these values would have different values in the different deployment sandboxes of a real system. Some other properties of our system, for example, whether our web API should display server errors or not, would also have different values in the different deployment sandboxes of a real system. Linx provides solution configuration Settings, to manage settings like these.
Add the following settings:
Name |
Type |
Value |
Database_connection |
String |
mongodb://localhost/Messaging_App |
Server_base_URL |
String |
http://localhost:8095/messaging_app/api |
Show_server_errors |
Boolean |
True |
Note that the base part of your database connection may differ, depending on the setup of your environment. Connect to your database with a tool like Robo 3T to establish the correct format.
The server base URL above assumes that port 8095 is available in your environment. If not, use another port that is available. It also assumes that the localhost hostname is configured in your environment. If not, you may have to use an IP address instead. For example 127.0.0.1 for your local machine, or use the ipconfig command to determine your local network IP address.
Web API Data
In order to move user and message data in and out of the web API methods and the database, we need to define exactly what user and message data looks like. Add a new folder to the Server project, and name it Data. Add a new custom type to the Data folder, and name it User. Add the following properties to the User type:
Name |
Type |
_id |
String |
Email |
String |
First_name |
String |
Last_name |
String |
Add a new custom type to the Data folder, and name it Message. Add the following properties to the Message type:
Name |
Type |
_id |
String |
User_ID |
String |
Text |
String |
Sent |
DateTime |
Is_read |
Boolean |
Define a Web API in Linx
Expand the REST plugin in the Plugin toolbox on the right. Add a SimpleRESTHost to the Server project, and set the following properties on it:
Property |
Value |
Name |
Web_service |
Base URI |
=$.Settings.Server_base_URL |
Show server errors |
=$.Settings.Show_server_errors |
Edit the Web_service Operations property, and add the following operations:
Name |
Path |
Method |
Request Body |
Response body
|
Add_user
|
/add_user
|
Post
|
Server.Data.User
|
None
|
Get_users
|
/get_users
|
Get
|
None
|
List<Server.Data.User>
|
Send_message
|
/send_message
|
Post
|
Server.Data.Message
|
None
|
Get_messages_for_user
|
/get_messages_for_user/{User_ID}
|
Get
|
None
|
List<Server.Data.Message>
|
Read_message
|
/read_message/{Message_ID}
|
Get
|
None
|
Server.Data.Message
|
Edit the Query string parameters property of the Get_messages_for_user operation, and add the following query string parameter:
Save the newly added query string parameter, and save the operations. Operation handlers are added to Web_service for each of the defined operations, in the Solution Explorer.
Implement the Add_User Web Method
To implement Add_user, we first check if the specified user’s email address already exists in our database, since we have a rule that duplicate email addresses are not permitted. If so, we return an error to indicate this. Otherwise, we persist with the specified user.
Select the Add_user handler. Add a MongoDBRead from the Plugin toolbox to the Add_user designer, and set the following properties:
Name |
Type |
Name
|
Retrieve_user_by_email
|
Connection string
|
=$.Settings.Database_connection
|
Collection
|
User
|
Operation
|
Find
|
Query
|
{Email:”@{$.Input.Data.body.Email}”}
|
Output type
|
Server.Data.User
|
Return options
|
First row, else empty row
|
Add an IfElse from the toolbox, after Retrieve_user_by_email, and name it If_email_exists. Edit its Conditions property and add one condition named True, with the value:
=Retrieve_user_by_email != $.System.Null
Add a ThrowException from the toolbox to the True path of If_email_exists, and name it Throw_error. Set its Message property to:
="A user with email address " + $.Input.Data.body.Email + " already exists."
Add a MongoDBWrite after If_email_exists, and set the following properties:
Property
|
Value
|
Name
|
Persist_new_user
|
Connection string
|
=$.Settings.Database_connection
|
Collection
|
User
|
Operation
|
Insert
|
Data
|
=$.Input.Data.body
|
Test the Get_Users Web Method
Now let’s test Add_user. Launch Postman, and add a new collection named Messaging App. Manage environments, at the top right, and add an environment, also named Messaging App. Add a new variable to it named Server_base_URL, and set its INITIAL VALUE to the web service base URL, in our case http://localhost:8095/messaging_app/api. Since the path of Add_user is /add_user, we will call it at {{Server_base_URL}}/add_user
.
Add a request to the Messaging App collection, with the following information:
Name
|
Add_user
|
HTTP method
|
POST
|
URL
|
{{Server_base_URL}}/add_user
|
Body format
|
raw
|
Body content type
|
JSON
|
Body text
|
{
“First_name” : “Joe”,
“Surname” : “Bloggs”,
“Email” : “joe.bloggs@example.com”
}
|
Note that the request body text is a JSON format instance of the User type, which we set as the request body type in the definition of our Add_user operation. In the Linx Designer, select the Web_service in the Solution Explorer, and DEBUG it from the toolbar. Once the debugger is ready, START it from the toolbar. Once the web service has started, our web API is listening at http://localhost:8095/messaging_app/api.
Send the Add_user request in Postman. If the request succeeds, the HTTP response code should be 200 OK. Launch Robo 3T and connect to your database server. The newly created user should appear in the Messaging_App database’s User collection. Send the Add_user request again. The HTTP response code should be 500 Internal Server Error, with an error that indicates that the specified email address already exists in the system. Add another user with the following request body:
STOP and CLOSE the debugger in the Linx Designer.
Implement the Get_Users Web Method
Select the Get_users handler. Add a MongoDBRead to the Get_users designer, and set the following properties:
Property
|
Value
|
Name
|
Retrieve_users
|
Connection string
|
=$.Settings.Database_connection
|
Collection
|
User
|
Operation
|
Find
|
Output type
|
Server.Data.User
|
Return options
|
List of rows
|
Next, add a SetValue from the standard Linx functions in the Plugin toolbar, and set the following properties:
Property
|
Value
|
Name
|
Set_response
|
Target
|
=$.Output.Data
|
Edit the Source property, and set the ResponseBody property to=Retrieve_users.
Test the Get_Users Web Method
In Postman, add another request to the Messaging App collection, with the following information:
Name
|
Get_users
|
HTTP method
|
GET
|
URL
|
{{Server_base_URL}}/get_users
|
In the Linx Designer, select the Web_service and start the debugger again. Send the Get_users request in Postman. The current list of users should be returned in JSON format. Stop and close the debugger in the Linx Designer.
Implement the Send_Message Web Method
To implement Send_message, we first check if the specified user exists in our database since we have a rule that a message must be for a user. If not, we return an error to indicate this. Otherwise, we stamp the Sent time on the message and then persist it. Select the Send_message handler. Add a MongoDBRead to the Send_message designer, and set the following properties:
Property
|
Value
|
Name
|
Retrieve_user_by_ID
|
Connection string
|
=$.Settings.Database_connection
|
Collection
|
User
|
Operation
|
Find
|
Query
|
{_id: ObjectId(“@{$.Input.Data.body.User_ID}”)}
|
Output type
|
Server.Data.User
|
Return options
|
First row, else empty row
|
Add an IfElse, and name it If_user_found. Edit its Conditions property and add one condition named False, with the value: =Retrieve_user_by_ID == $.System.Null
Add a ThrowException to the False path of If_user_found, and name it Throw_error. Set its Message property to: =”User with ID ” + $.Input.Data.body.User_ID + ” not found.” Add an instance of the Message data type from the Solution Explorer, after If_user_found, and set the following properties:
Property
|
Value
|
Name
|
new_message
|
Value
|
=$.Input.Data.body
|
Add a SetValue, and set the following properties:
Property
|
Value
|
Name
|
Set_new_message_sent_time
|
Target
|
=new_message.Sent
|
Source
|
=$.System.CurrentDateTime
|
Add a MongoDBWrite, and set the following properties:
Property
|
Value
|
Name
|
Persist_new_message
|
Connection string
|
=$.Settings.Database_connection
|
Collection
|
Message
|
Operation
|
Insert
|
Data
|
=new_message
|
Test the Send_Message Web Method
In Postman, add a request with the following information:
Name
|
Send_message
|
HTTP method
|
POST
|
URL
|
{{Server_base_URL}}/send_message
|
Body format
|
raw
|
Body content type
|
JSON
|
Body text
|
{
“User_ID”: “5faa6094bc6e2d10a89fcbaf”,
“Text”: “Hello world.”
}
|
As before, the request body text is a JSON format instance of the Message type, which we set as the request body type in the definition of our Send_message operation. In the Linx Designer, select the Web_service and start the debugger again. Send the Send_message request in Postman. This request should fail with HTTP status code 500, and an error to indicate that the user with the specified ID is not found. This is because the User_ID in the Send_message request body sample, highlighted in red above, is from my database. You will have to change this to use the correct value from your database.
In Robo 3T, edit the document of the first user in the User collection. Copy the value of the ObjectId, and replace the User_ID in the Send_message request body in Postman. Send the Send_message request again. This time the HTTP response code should be 200 OK. Launch Robo 3T and connect to your database server. The newly sent message should appear in the Message collection. Send the Send_message request once more. This should succeed because there is no natural key violation. Stop and close the debugger in the Linx Designer.
Implement the Get_Messages_for_User Web Method
To implement Get_messages_for_user, we first check that a value has been provided for the user ID since this is a required parameter. If not, we return an error to indicate this. Otherwise, we check if a value has been provided for the Is_read parameter, which is optional. If so, we format a query filter for the Is_read field of the message collection in the database. Then we retrieve the messages in descending sent time order and return them.
Select the Get_messages_for_user handler. Add an IfElse to the Get_messages_for_user designer, and name it If_user_ID_is_empty. Edit its Conditions property, and add a condition named True, with the value: =$.Input.Data.User_ID == $.System.Null || $.Input.Data.User_ID.Trim() == “”
Add a ThrowException to the True path of If_user_ID_is_empty, and set the following properties:
Property
|
Value
|
Name
|
Throw_error
|
Message
|
User_ID is required.
|
Add a String variable from the standard Linx plugins, after If_user_ID_is_empty, and name it is_read_query_filter. Add an IfElse, and name it If_is_read_parameter_has_value. Edit its Conditions property, and add a condition named True, with the value:
=$.Input.Data.Is_read != $.System.Null && ($.Input.Data.Is_read.Trim().ToLower() == “false” || $.Input.Data.Is_read.Trim().ToLower() == “true”)
Add a SetValue to the True path of If_is_read_parameter_has_value, and set the following properties:
Property
|
Value
|
Name
|
Set_is_read_query_filter
|
Target
|
=is_read_query_filter
|
Source
|
=”, \”Is_read\” : ” + $.Input.Data.Is_read
|
Add a MongoDBRead after If_is_read_parameter_has_value, and set the following properties:
Property
|
Value
|
Name
|
Retrieve_messages_by_user_ID
|
Connection string
|
=$.Settings.Database_connection
|
Collection
|
Message
|
Operation
|
Find
|
Query
|
{
User_ID:”@{$.Input.Data.User_ID}”
@{is_read_query_filter}
}
|
Sort
|
{ Sent: -1 }
|
Output type
|
Server.Data.Message
|
Return options
|
List of rows
|
Add a SetValue, and set the following properties:
Property
|
Value
|
Name
|
Set_response
|
Target
|
=$.Output.Data
|
Edit the Source property, and set the ResponseBody property to =Retrieve_messages_by_user_ID
Test the Get_Messages_for_User Web Method
In Postman, add another request with the following information:
Name
|
Get_messages_for_user
|
HTTP method
|
GET
|
URL
|
{{Server_base_URL}}/get_messages_for_user/5faa6094bc6e2d10a89fcbaf?Is_read=false
|
In the request Params of the Get_messages_for_user request, add a parameter with KEY Is_read and VALUE false. In the Linx Designer, select the Web_service and start the debugger.
Send the Get_messages_for_user request in Postman. This request should succeed but will return no messages. This is because the user ID in the Get_messages_for_user request URL sample, highlighted in red above, is from my database. You will have to change this to use the correct value from your database.
In Robo 3T, edit the document of the user in the User collection, to which we have sent messages previously. Copy the value of the ObjectId, and replace the user ID in the Get_messages_for_user request URL in Postman. Now send the Get_messages_for_user request again. The current list of messages for the specified user should be returned in JSON format. Stop and close the debugger in the Linx Designer.
Implement the Read_Message Web Method
To implement Read_message, we try to retrieve the message by the provided message ID. If the message is not found, we return an error to indicate this. This will cover the case where the Message_ID, which is a required parameter, has not been provided. Otherwise, we check if the message has been read before. If not, we mark it as read. Then we return the message.
Select the Read_message handler. Add a MongoDBRead to the Read_message designer, and set the following properties:
Property
|
Value
|
Name
|
Retrieve_message_by_ID
|
Connection string
|
=$.Settings.Database_connection
|
Collection
|
Message
|
Operation
|
Find
|
Query
|
{_id: ObjectId(“@{$.Input.Data.Message_ID}”)}
|
Output type
|
Server.Data.Message
|
Return options
|
First row, else empty row
|
Add an IfElse, and name it If_message_found. Edit its Conditions property, and add a condition named False, with the value: =Retrieve_message_by_ID == $.System.Null
Add a ThrowException to the False path of If_message_found, and set the following properties:
Add another IfElse after If_message_found, and name it If_message_is_read. Edit its Conditions property, and add a condition named False, with the value: =Retrieve_message_by_ID.Is_read == false
Property
|
Value
|
Name
|
Throw_error
|
Message
|
=”Message with ID ” + $.Input.Data.Message_ID + ” not found.”
|
Add an instance of a message data type from the Solution Explorer, to the False path of If_message_is_read, and set the following properties:
Add a SetValue to the False path of If_message_is_read, and set the following properties:
Property
|
Value
|
Name
|
Mark_message_as_read
|
Target
|
=read_message.Is_read
|
Source
|
True
|
Add a MongoDBWrite to the False path of If_message_is_read, and set the following properties:
Property
|
Value
|
Name
|
Persist_read_message
|
Connection string
|
=$.Settings.Database_connection
|
Collection
|
Message
|
Operation
|
Replace
|
Data
|
=read_message
|
Add a SetValue after If_message_is_read, and set the following properties:
Property
|
Value
|
Name
|
Set_response
|
Target
|
=$.Output.Data
|
Edit the Source property, and set the ResponseBody property to =Retrieve_message_by_ID
Test the Read_Message Web Method
In Postman, add another request with the following information:
Name
|
Read_message
|
HTTP method
|
GET
|
URL
|
{{Server_base_URL}}/read_message/5faa7e92bc6e2d10a89fd093
|
In the Linx Designer, select the Web_service and start the debugger. Send the Read_message request in Postman. This request should fail with an error indicating that a message with the specified ID is not found. This is because the message ID in the Read_message request URL sample, highlighted in red above, is from my database. You will have to change this to use the correct value from your database.
In Robo 3T, edit the document of the first message in the Message collection. Copy the value of the ObjectId, and replace the message ID in the Read_message request URL in Postman.
Now send the Read_message request again. The message with the specified ID should be returned in JSON format. Stop and close the debugger in the Linx Designer.
Voila! You’re done.