Introduction
In this tutorial, we will build a simple RESTful API for managing books using Nolang. We'll define the data schema for a book, set up HTTP endpoints for CRUD operations, and create a user-friendly frontend using HTML and Bootstrap to interact with the API.
Prerequisites
- Basic understanding of RESTful APIs
- Familiarity with JSON and HTML
- Nolang executable installed on your machine (Windows or Linux)
- MongoDB installed and running locally
Project Setup
Step 1: Set Up Your Nolang Project
-
Create a new directory for your project:
mkdir nolang-restful-api cd nolang-restful-api
-
Create a file named
app.json
with the following content:
{ "name": "RESTful API Sample", "schemas": [ { "$id": "book", "properties": { "Title": { "type": "string", "minLength": 1 }, "Author": { "type": "string", "minLength": 5 }, "PublishedDate": { "type": "date" }, "Summary": { "type": "string", "minLength": 20, "maxLength": 200 } }, "required": ["Title", "Author", "PublishedDate"] } ], "storage": { "adapter": "mongodb", "url": "mongodb://localhost:27017", "database": "nolangtest", "id": "_id" }, "endpoints": [ { "type": "http", "port": 3000, "static": "./public", "routes": [ { "path": "/book/list", "method": "get", "return": { "$$schema": "book", "$$header": { "action": "R" } } }, { "path": "/book/:id", "method": "get", "return": { "$$schema": "book", "$$header": { "action": "R", "filter": { "$$objid": "{{env.request.params.id}}" } } } }, { "path": "/book/create", "method": "post", "bodyParser": "urlencoded", "return": { "$$schema": "book", "$$header": { "action": "C" }, "Title": "{{env.request.body.Title}}", "Author": "{{env.request.body.Author}}", "PublishedDate": "{{env.request.body.PublishedDate}}", "Summary": "{{env.request.body.Summary}}" } }, { "path": "/book/update/:id", "method": "put", "bodyParser": "urlencoded", "return": { "$$schema": "book", "$$header": { "action": "U", "filter": { "$$objid": "{{env.request.params.id}}" } }, "Title": "{{env.request.body.Title}}", "Author": "{{env.request.body.Author}}", "PublishedDate": "{{env.request.body.PublishedDate}}", "Summary": "{{env.request.body.Summary}}" } }, { "path": "/book/delete/:id", "method": "delete", "return": { "$$schema": "book", "$$header": { "action": "D", "filter": { "$$objid": "{{env.request.params.id}}" } } } } ] } ] }
-
Create a
public
directory and add a file namedindex.html
with the following content:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Book Management App</title> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> <style> body { padding: 20px; } table { margin-top: 20px; } form { margin-bottom: 20px; } </style> </head> <body> <div class="container"> <h1 class="text-center">Book Management App</h1> <!-- Create Book Form --> <form id="createBookForm" class="mb-4"> <h2>Create a New Book</h2> <div class="form-group"> <label for="createTitle">Title:</label> <input type="text" id="createTitle" name="Title" class="form-control" required> </div> <div class="form-group"> <label for="createAuthor">Author:</label> <input type="text" id="createAuthor" name="Author" class="form-control" required> </div> <div class="form-group"> <label for="createPublishedDate">Published Date:</label> <input type="date" id="createPublishedDate" name="PublishedDate" class="form-control" required> </div> <div class="form-group"> <label for="createSummary">Summary:</label> <textarea id="createSummary" name="Summary" class="form-control" required></textarea> </div> <button type="submit" class="btn btn-primary">Create Book</button> </form> <!-- Update Book Form --> <form id="updateBookForm" class="mb-4" style="display: none;"> <h2>Update a Book</h2> <input type="hidden" id="updateId" name="id"> <div class="form-group"> <label for="updateTitle">Title:</label> <input type="text" id="updateTitle" name="Title" class="form-control" required> </div> <div class="form-group"> <label for="updateAuthor">Author:</label> <input type="text" id="updateAuthor" name="Author" class="form-control" required> </div> <div class="form-group"> <label for="updatePublishedDate">Published Date:</label> <input type="date" id="updatePublishedDate" name="PublishedDate" class="form-control" required> </div> <div class="form-group"> <label for="updateSummary">Summary:</label> <textarea id="updateSummary" name="Summary" class="form-control" required></textarea> </div> <button type="submit" class="btn btn-primary">Update Book</button> <button type="button" class="btn btn-secondary" onclick="cancelUpdate()">Cancel</button> </form> <!-- List of Books --> <h2>List of Books</h2> <table id="bookTable" class="table table-striped"> <thead> <tr> <th>Title</th> <th>Author</th> <th>Published Date</th> <th>Summary</th> <th>Actions</th> </tr> </thead> <tbody> <!-- Books will be dynamically added here --> </tbody> </table> </div> <!-- Bootstrap JS, Popper.js, and jQuery --> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> <script> const bookTable = document.getElementById('bookTable').getElementsByTagName('tbody')[0]; const createBookForm = document.getElementById('createBookForm'); const updateBookForm = document.getElementById('updateBookForm'); // Fetch and display books async function fetchBooks() { const response = await fetch('/book/list'); const books = await response.json(); bookTable.innerHTML = ''; books.forEach(book => { const row = bookTable.insertRow(); row.innerHTML = ` <tr> <td>${book.Title}</td> <td>${book.Author}</td> <td>${book.PublishedDate}</td> <td>${book.Summary}</td> <td> <button class="btn btn-warning btn-sm" onclick="editBook('${book.$$objid}')">Edit</button> <button class="btn btn-danger btn-sm" onclick="deleteBook('${book.$$objid}')">Delete</button> </td> </tr> `; }); } // Create book createBookForm.addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(createBookForm); const response = await fetch('/book/create', { method: 'POST', body: new URLSearchParams(formData) }); if (response.ok) { fetchBooks(); createBookForm.reset(); } }); // Edit book async function editBook(id) { const response = await fetch(`/book/${id}`); const books = await response.json(); const book = books[0]; document.getElementById('updateId').value = book.$$objid; document.getElementById('updateTitle').value = book.Title; document.getElementById('updateAuthor').value = book.Author; document.getElementById('updatePublishedDate').value = book.PublishedDate; document.getElementById('updateSummary').value = book.Summary; createBookForm.style.display = 'none'; updateBookForm.style.display = 'block'; } // Update book updateBookForm.addEventListener('submit', async (e) => { e.preventDefault(); const id = document.getElementById('updateId').value; const formData = new FormData(updateBookForm); const response = await fetch(`/book/update/${id}`, { method: 'PUT', body: new URLSearchParams(formData) }); if (response.ok) { fetchBooks(); updateBookForm.reset(); updateBookForm.style.display = 'none'; createBookForm.style.display = 'block'; } }); // Cancel update function cancelUpdate() { updateBookForm.reset(); updateBookForm.style.display = 'none'; createBookForm.style.display = 'block'; } // Delete book async function deleteBook(id) { const response = await fetch(`/book/delete/${id}`, { method: 'DELETE' }); if (response.ok) { fetchBooks(); } } // Initial fetch of books fetchBooks(); </script> </body> </html>
Step 2: Run Your Nolang Project
Use the Nolang executable to run your project:
nolang app.json
This will start your Nolang server on port 3000, as specified in the app.json
file.
Understanding Endpoint Routes and the Return Key
The app.json
file defines the HTTP endpoints for your API. Each endpoint specifies the following keys:
- Path: The URL pattern for the endpoint.
- Method: The HTTP method (GET, POST, PUT, DELETE) used for the endpoint.
- BodyParser: (Optional) Specifies how the request body should be parsed. Common values are "urlencoded" or "json".
- Return: The configuration for what data to return and how to process it.
Example Endpoint Route
{
"path": "/book/create",
"method": "post",
"bodyParser": "urlencoded",
"return": {
"$$schema": "book",
"$$header": {
"action": "C"
},
"Title": "{{env.request.body.Title}}",
"Author": "{{env.request.body.Author}}",
"PublishedDate": "{{env.request.body.PublishedDate}}",
"Summary": "{{env.request.body.Summary}}"
}
}
Breakdown of the Return Key
- $$schema: Specifies the schema to use for the data. In this case, it's the "book" schema.
-
$$header: Contains metadata about the action to perform.
"action": "C"
indicates a create action. - Other fields (Title, Author, etc.): These fields map the incoming request data to the schema properties.
Common Actions in Return Header
- C (Create): Create a new entry in the database.
- R (Read): Retrieve data from the database.
- U (Update): Update an existing entry in the database.
- D (Delete): Remove an entry from the database.
Using the Frontend
Create a Book
- Open your browser and navigate to
http://localhost:3000
. - Fill out the "Create a New Book" form with the book details.
- Click the "Create Book" button to add the book to the database.
List Books
- The list of books will be displayed under the "List of Books" section.
- Each book entry will have "Edit" and "Delete" buttons for further actions.
Update a Book
- Click the "Edit" button next to the book you want to update.
- The book details will be populated in the "Update a Book" form.
- Make the necessary changes and click the "Update Book" button to save the changes.
- Click the "Cancel" button to return to the book creation form.
Delete a Book
- Click the "Delete" button next to the book you want to remove.
- The book will be deleted from the database and removed from the list.
Using Postman to Test Endpoints
Postman is a popular tool used to test and interact with APIs. Here's how you can use Postman to test the endpoints of your RESTful API.
Step 1: Download and Install Postman
- Download Postman from the official Postman website.
- Install Postman on your computer.
Step 2: Test Endpoints
Test the Create Endpoint
- Open Postman and create a new POST request.
- Set the URL to
http://localhost:3000/book/create
. - Under the "Body" tab, select "x-www-form-urlencoded".
- Add the following key-value pairs:
-
Title
: (Enter a book title) -
Author
: (Enter the author's name) -
PublishedDate
: (Enter the published date in YYYY-MM-DD format) -
Summary
: (Enter a summary of the book)
-
- Click the "Send" button to create a new book entry.
Test the List Endpoint
- Create a new GET request.
- Set the URL to
http://localhost:3000/book/list
. - Click the "Send" button to retrieve the list of books.
Test the Update Endpoint
- Create a new PUT request.
- Set the URL to
http://localhost:3000/book/update/{id}
(Replace{id}
with the ID of the book you want to update). - Under the "Body" tab, select "x-www-form-urlencoded".
- Add the key-value pairs with the updated book information.
- Click the "Send" button to update the book entry.
Test the Delete Endpoint
- Create a new DELETE request.
- Set the URL to
http://localhost:3000/book/delete/{id}
(Replace{id}
with the ID of the book you want to delete). - Click the "Send" button to delete the book entry.
Conclusion
In this tutorial, we built a RESTful API for managing books using Nolang. We defined the data schema, set up HTTP endpoints for CRUD operations, and created a frontend to interact with the API. Feel free to explore additional features and improvements to enhance the application further.
Top comments (0)