NODA is a RESTful task management web API built with Go and designed to simplify the process of managing tasks, lists, and user interactions. It uses PostgreSQL for data storage, JSON Web Tokens (JWT) for authorization and security, and Docker Compose for easy development.
- Table of contents
- Source code structure
- Installation
- Debugging
- Running the tests
- Screenshots
- API endpoints
- Recommendations
For structuring the project source code I'm following a simple and pragmatic approach that has work very well for me.
Personally, I prefer to wire up everything in the main.go
file and avoid having separate files for
database configuration, server configuration and API routing, as I believe this is a more elegant and cleaner
approach compared to having several dispersed packages/files.
While I have a preference for wiring everything in main.go
, it might be a wiser practice for very large
codebases to implement separation of concerns.
.
├── assets
├── client
├── data
│ ├── model
│ ├── transfer
│ └── types
├── database
├── docs
├── failure
├── global
├── handler
├── mocks
├── repository
└── service
NOTE: Another approach would be to move the
model
,transfer
andtypes
directories (packages) to the root directory; this approach, however, provides the opportunity to refer these packages in a more natural way as in “data types”.
- assets: Contains images and other similar assets.
- client: Contains the implementation of a client for this web API (not implemented yet.)
- data
- database: Contains the database source code.
- docs: Holds detailed API documentation and usage (out of date).
- failure: Manages error handling and custom error definitions to standardize responses. ( See Recommendations.)
- global: Contains globally accessible constants, especially if they're coming from environment variable.
- handler: Implements the HTTP request handlers.
- mocks: Contains mock implementations for unit testing.
- repository: Defines the data access layer for interactions with the database.
- service: Contains the business logic layer for core functionalities and validations.
I decided to move the database scripts to a different repository as I think this it's easier to maintain, version control, scale and reuse. However, since I still need it, I make it accessible from this repository as a Git submodule.
To run the project you require the database to be running. Once you've finished all the following steps, you must end up having something like:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5d7ce9611cfc postgres:16rc1-alpine3.18 "docker-entrypoint.s…" 3 seconds ago Up 2 seconds (health: starting) 0.0.0.0:7891->5432/tcp, :::7891->5432/tcp noda_database
d8f3bcf2c1ae golang:1.22.5-alpine3.20 "sh -c ' cd /src && …" 3 seconds ago Up 2 seconds 0.0.0.0:7890->7890/tcp, :::7890->7890/tcp noda_backend
- Docker
- Docker Compose
- Git
-
Clone this repository and its submodules:
git clone --recurse-submodules [email protected]:fontseca/noda-tasks-management-tool-api.git cd noda-tasks-management-tool-api
-
Run the Docker containers:
docker compose up --detach
In the backend service, wait for the project packages to be installed before running the server.
docker logs noda_backend --follow Installing Go dependencies... Go dependencies installed. ^C
-
Bootstrap the database objects:
docker exec --interactive --tty noda_database sh -c '$PROJECT_DIR/bootstrap.sh'
-
If the database objects are created successfully, then access the backend container and from there you can either build or run the web API server:
docker exec --interactive --tty noda_backend sh cd /src go run .
Now you can start making requests to the web API at http://0.0.0.0:7890
.
First, install Delve inside the container, if not installed yet:
docker exec --interactive --tty noda_backend go install github.com/go-delve/delve/cmd/dlv@latest
Once installed, start the debugging server:
docker exec --interactive --tty noda_backend sh -c 'cd /src && dlv debug --listen=:$SERVER_PORT --headless=true --api-version=2'
Note: The debugging server uses the same port as the actual application.
You run the tests of at leas one of the packages repository
, service
and handler
with the command:
go test ./repository/ ./service/ ./handler/
ok noda/repository 0.051s
ok noda/service 0.745s
ok noda/handler 0.029s
Or, if you prefer using gotestsum:
gotestsum ./service/ ./repository ./handler
✓ handler (29ms)
✓ repository (48ms)
✓ service (643ms)
DONE 699 tests in 6.567s
Actor | HTTP Method | Endpoint | Description |
---|---|---|---|
Any | POST |
/signup |
Create a new user. |
User | POST |
/signin |
Log in an existent user. |
User | POST |
/me/logout |
Log out the current user. |
User | POST |
/me/change_password |
Change the password of the logged in user. |
Actor | HTTP Verb | Endpoint | Description |
---|---|---|---|
Admin | GET |
/users |
Retrieve all users. |
Admin | GET |
/users/search |
Search for users. |
Admin | GET |
/users/{user_uuid} |
Retrieve a user. |
Admin | DELETE |
/users/{user_uuid} |
Permanently remove a user and all its related data. |
Admin | PUT |
/users/{user_uuid}/block |
Block one user. |
Admin | DELETE |
/users/{user_uuid}/block |
Unblock one user. |
Admin | GET |
/users/blocked |
Retrieve all blocked users. |
User | GET |
/me |
Get the logged in user. |
User | PUT |
/me |
Partially update the account of the logged in user. |
User | DELETE |
/me |
Permanently remove the account of the logged in user. |
User | GET |
/me/settings |
Retrieve all the settings of the logged in user. |
Actor | HTTP Method | Endpoint | Description |
---|---|---|---|
User | GET |
/me/groups |
Retrieve all the groups. |
User | POST |
/me/groups |
Create a new group. |
User | GET |
/me/groups/{groups_id} |
Retrieve a group. |
User | PATCH |
/me/groups/{groups_id} |
Partially update a list. |
User | DELETE |
/me/groups/{groups_id} |
Permanently remove a list and all related data. |
User | GET |
/me/groups/{groups_id} |
Retrieve a list. |
Actor | HTTP Method | Endpoint | Description |
---|---|---|---|
User | GET |
/me/lists |
Retrieve all the ungrouped lists. |
User | POST |
/me/lists |
Create a new ungrouped list. |
User | GET |
/me/lists/{list_uuid} |
Retrieve a ungrouped list. |
User | PATCH |
/me/lists/{list_uuid} |
Partially update a ungrouped list. |
User | DELETE |
/me/lists/{list_uuid} |
Permanently remove an ungrouped list and all related data. |
User | GET |
/me/groups/{group_uuid}/lists |
Retrieve all the lists of a group. |
User | POST |
/me/groups/{group_uuid}/lists |
Create a new list for a group. |
User | GET |
/me/groups/{group_uuid}/lists/{list_uuid} |
Retrieve a list of a group. |
User | PATCH |
/me/groups/{group_uuid}/lists/{list_uuid} |
Partially update a list of a group. |
User | DELETE |
/me/groups/{group_uuid}/lists/{list_uuid} |
Permanently remove a list of a group and all related data. |
Actor | HTTP Method | Endpoint | Description |
---|---|---|---|
User | GET |
/me/today |
Retrieve all the tasks from the Today list. |
User | GET |
/me/tomorrow |
Retrieve all the tasks for tomorrow. |
User | GET |
/me/tasks |
Retrieve all the tasks. |
User | POST |
/me/tasks |
Create a new task and store in the Today list. |
User | GET |
/me/tasks/search |
Search for tasks. |
User | GET |
/me/tasks/completed |
Retrieve all the completed tasks. |
User | GET |
/me/tasks/archived |
Retrieve archived tasks. |
User | GET |
/me/tasks/trashed |
Retrieve trashed tasks. |
User | GET |
/me/tasks/{task_uuid} |
Retrieve a task. |
User | PATCH |
/me/tasks/{task_uuid} |
Partially update a task. |
User | DELETE |
/me/tasks/{task_uuid} |
Permanently remove a task and all related data. |
User | PUT |
/me/tasks/{task_uuid}/trash |
Move a task to trash. |
User | DELETE |
/me/tasks/{task_uuid}/trash |
Recover a task from trash. |
User | PUT |
/me/tasks/{task_uuid}/reorder |
Rearrange a task in its list. |
User | GET |
/me/lists/{list_uuid}/tasks |
Retrieve all the tasks of an ungrouped list. |
User | POST |
/me/lists/{list_uuid}/tasks |
Create a task and save it in an ungrouped list. |
User | GET |
/me/groups/{group_uuid}/lists/{list_uuid}/tasks |
Retrieve all the tasks of a list in a group. |
User | POST |
/me/groups/{group_uuid}/lists/{list_uuid}/tasks |
Create a task and save it in a list within a group. |
Actor | HTTP Method | Endpoint | Description |
---|---|---|---|
User | GET |
/me/tasks/{task_uuid}/steps |
Retrieve the steps to achieve a task. |
User | POST |
/me/tasks/{task_uuid}/steps |
Add a new step to achieve a task. |
User | PATCH |
/me/tasks/{task_uuid}/steps/{step_uuid} |
Partially update a step. |
User | PUT |
/me/tasks/{task_uuid}/steps/{step_uuid}/accomplish |
Mark a step as accomplished. |
User | DELETE |
/me/tasks/{task_uuid}/steps/{step_uuid}/accomplish |
Unmark a step as accomplished. |
User | DELETE |
/me/tasks/{task_uuid}/steps/{step_uuid} |
Permanently remove a step from a task. |
User | POST |
/me/tasks/{task_uuid}/steps/{step_uuid}/reorder |
Rearrange a step in a task. |
Actor | HTTP Method | Endpoint | Description |
---|---|---|---|
User | GET |
/me/tags |
Retrieve the steps to achieve a task. |
User | POST |
/me/tags |
Create a new tag. |
User | PATCH |
/me/tags/{tag_uuid} |
Partially update a tag. |
User | DELETE |
/me/tags/{tag_uuid} |
Permanently remove a tag. |
Actor | HTTP Method | Endpoint | Description |
---|---|---|---|
User | GET |
/me/tasks/{task_uuid}/attachments |
Retrieve all the attachments in a task (if any). |
User | GET |
/me/tasks/{task_uuid}/attachments/{attachment_uuid} |
Get an attachments in a task. |
User | DELETE |
/me/tasks/{task_uuid}/attachments/{attachment_uuid} |
Permanently remove an attachments in a task. |
If in doubt about how to transmit error messages to the clients of your web API, use the RFC 9457: Problem Details for HTTP APIs specification. I didn't know about it by the time I started this project and instead used a similar approach inspired by the PostgreSQL style.