How to create an Rest API using C

Pedro Fonseca
4 min readSep 29, 2024

--

Have you ever wondered how a REST API really works under the hood? Implementing an API in a low-level language can be challenging, especially when dealing with C, where basic resources like Strings don’t exist, and even the most well-known format for communication interfaces, JSON, has no native support. Did you know that it’s even possible to connect directly to a database using C? I will be demonstrating this and other concepts here in this article. (This article is based on repo rest-api-c)

Have you heard of libmicrohttpd? It’s a C library designed to simplify the creation of HTTP servers. When working with low-level languages like C, building a server from scratch can be quite complex, especially due to the lack of abstractions found in higher-level languages. That’s where libmicrohttpd shines: it provides a simple layer for building efficient HTTP servers without having to deal with the more complicated details, like manually managing sockets or the HTTP protocol. And the best part? It’s extremely lightweight, designed for use in applications that demand high performance and low resource consumption, such as embedded systems. If you’re considering building a REST API in C, libmicrohttpd is a great tool to efficiently handle HTTP requests and responses.

In this project i use libpq,the official C library for PostgreSQL, designed to allow developers to directly connect to the database, execute queries, and manipulate results with high efficiency. While working with C requires more attention to memory management and data structures, libpq provides full control over database communication without the abstraction layers found in higher-level languages. This makes it an ideal choice for applications that require high performance and detailed control over database transactions and operations.

Now that I’ve presented the main libraries used in this project (excluding the basic libraries for working with C, such as stdio, stdlib, string, etc.), let’s discuss the project itself. Initially, it is quite simple, focusing on initializing the API and releasing the port for connections. Below is the code:

#include <stdio.h>
#include <string.h>
#include <microhttpd.h>
#include "headers/handler.h"

#define PORT 8080

int main() {
printf("Starting server on port %d\n", PORT);

struct MHD_Daemon *daemon;

daemon = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, PORT, NULL, NULL,
&default_handler, NULL, MHD_OPTION_END);
if (!daemon)
return 1;

while (1)
sleep(1);

MHD_stop_daemon(daemon);

return 0;
}

In this example, we are setting up a simple HTTP server using the libmicrohttpd library. The server is started on port 8080 and manages connections in separate threads, thanks to the MHD_USE_THREAD_PER_CONNECTION flag. The default_handler function will be called to process requests. The while loop keeps the server active until it is interrupted, and finally, the daemon is stopped with MHD_stop_daemon. This is a basic structure, but it already establishes a foundation for creating a functional API.

The code below represents the default_handler function, which is responsible for processing incoming requests to the HTTP server. It receives various details about the connection, such as the URL, the HTTP method (GET, POST, etc.), and upload data, in addition to logging the API call.

enum MHD_Result default_handler(
void *cls, struct MHD_Connection *connection,
const char *url, const char *method, const char *version,
const char *upload_data, size_t *upload_data_size, void **con_cls) {

char *url_str = (char *)url;
char *method_str = (char *)method;
int ret;

struct MHD_Response *response;
HTTP_response response_api;

log_api(url_str, method_str); // Log the request URL and method

TRY {
if (strcmp(url_str, "/") == 0) {
response_api = (HTTP_response){
.body = simple_message("Hello world!"),
.status = OK
};
}

else if (validate_route(url_str, "/users")) {
response_api = user_router(url_str, method_str, upload_data);
}

else {
response_api = (HTTP_response){
.body = simple_message("Not found"),
.status = NOT_FOUND
};
}
} CATCH {
response_api = (HTTP_response){
.body = simple_message("Internal server error"),
.status = INTERNAL_SERVER_ERROR
};

printf("Internal server error");
}

response = HTTP_build_response_JSON(response_api.body); // Build the HTTP response

if (!response)
return MHD_NO;

ret = MHD_queue_response(connection, response_api.status, response); // Queue the response
MHD_destroy_response(response); // Free the response memory

return ret; // Return the result
}

In this function, the code starts by logging the request using log_api. It then checks if the URL matches the root ("/"). If it does, it responds with a simple "Hello world!" message and a success status (200 OK). If the URL is valid for the /users resource, the user_router function is called to process the request. For unrecognized URLs, the function returns a "Not found" message (404 Not Found).

If an error occurs during processing, an internal server error (500 Internal Server Error) is returned, along with a log in the console. Finally, the HTTP response is built in JSON format using HTTP_build_response_JSON, and the response is queued to be sent to the client. The memory allocated for the response is freed after queuing.

These are the basic principles of how this API works. Based on this, by taking a brief look at the functionality of the rest-api-c repository, you can start advancing your own API implementation. I recommend visiting the repository, and if you find anything or have any questions, don’t hesitate to contact me! Additionally, I suggest exploring how JSON responses are constructed and how route management is handled in the API.

--

--

Pedro Fonseca
Pedro Fonseca

Written by Pedro Fonseca

Computer Scientist | Full Stack Software Developer

No responses yet