← Articles

Systems Programming

AVL Trees C CRUD Data Structures

Building an In-Memory Record Database in C (Part 8)

Part 8 of creating an in-memory record database in C. This article goes over the creation of the console user interface.

Published Sep 12, 2026

In-Memory Record Database

Want to view this project? In-Memory Record Database

Creating the Console UI

We can start bringing everything together with the console user interface. Feel free to skip this article if you have some different ideas on how you’d like to do your user interface. I’m not going to go into too much detail on input validation unless necessary. There’s a lot of ways to validate input, so pick whichever one you’re most comfortable with as long as it works the way you intend in your program.

To make our lives a bit easier, let’s give our program a function that pre-creates records for us that way we can actually test some data out:

static void preCreateRecords(struct RecordTable *table)
{
    char buffer[200];

    while (1)
    {
        int input = 0;

        printf("Do you want to pre-fill the table with test records?\n");
        printf("1. Yes\n");
        printf("2. No\n");

        if (fgets(buffer, sizeof(buffer), stdin) == NULL)
        {
            printf("\n*** Please enter a valid input! ***\n");
            continue;
        }

        size_t inputLength = strlen(buffer);

        if (inputLength > 0 && buffer[inputLength - 1] == '\n')
        {
            buffer[inputLength - 1] = '\0';
        }
        else if (inputLength == sizeof(buffer) - 1)
        {
            int ch;

            while ((ch = getchar()) != '\n' && ch != EOF)
            {
            }

            printf("\n*** Please enter a valid input! ***\n");

            continue;
        }

        if (inputLength == 0)
        {
            printf("\n*** Input cannot be empty! ***\n");

            continue;
        }

        char trashData;
        int parsed = sscanf(buffer, "%d %c", &input, &trashData);

        if (parsed != 1)
        {
            printf("\n*** Please enter a valid input! ***\n");

            continue;
        }

        switch (input)
        {
        case 1:
            createRecord(table, "Liam", "Carter", 28);
            createRecord(table, "Olivia", "Bennett", 34);
            createRecord(table, "Liam", "Mitchell", 22);
            createRecord(table, "Emma", "Carter", 45);
            createRecord(table, "Ethan", "Parker", 28);
            createRecord(table, "Olivia", "Collins", 27);
            createRecord(table, "Mason", "Turner", 34);
            createRecord(table, "Emma", "Bennett", 19);
            createRecord(table, "Lucas", "Parker", 45);
            createRecord(table, "Liam", "Hayes", 28);
            createRecord(table, "James", "Carter", 63);
            createRecord(table, "Olivia", "Turner", 27);
            createRecord(table, "Benjamin", "Bennett", 34);
            createRecord(table, "Emma", "Collins", 45);
            createRecord(table, "Henry", "Parker", 27);
            break;

        case 2:
            break;

        default:
            printf("\n*** Please enter a valid input! ***\n");
            continue;
        }

        clearScreen();

        break;
    }
}

I know this is a bit clunky, but it works for my purposes. Feel free to use this directly if you’d like.

Now let’s create our main menu screen. This function is going to be the only thing that’s actually called in main.c. This way, all of our functionality is separated accordingly and we can keep main.c light. The entire file should be nothing more than this:

#include "../include/console_ui.h"

int main(void)
{
    mainMenu();


    return 0;
}

So in our mainMenu function, we’ll initialize our record table with initializeRecordTable which we looked at all the way back in part 3. Here’s the code if you don’t have it already:

void initializeRecordTable(struct RecordTable *table)
{
    table->idTree.root = NULL;
    table->idTree.comparator = compareById;

    table->firstNameTree.root = NULL;
    table->firstNameTree.comparator = compareByFirstName;

    table->lastNameTree.root = NULL;
    table->lastNameTree.comparator = compareByLastName;

    table->ageTree.root = NULL;
    table->ageTree.comparator = compareByAge;
}

Then, we’ll call preCreateRecords to decide if we want to pre-fill the record table. Finally, we’ll print the main menu.

void mainMenu(void)
{
    char buffer[100];
    int selection = 0;

    struct RecordTable table;

    printf("Initializing Record table...\n");

    initializeRecordTable(&table);

    printf(">>> Record table ready! <<<\n\n");

    preCreateRecords(&table);

    while (selection != 6)
    {
        printf("===============================\n");
        printf("        RECORD DATABASE        \n");
        printf("===============================\n");
        printf("1. Create Record\n");
        printf("2. Edit Record\n");
        printf("3. Delete Record\n");
        printf("4. Search for a Record\n");
        printf("5. List Records\n");
        printf("------\n");
        printf("6. Exit\n\n\n");

        printf("Select an option: ");

        if (fgets(buffer, sizeof(buffer), stdin) == NULL)
        {
            printf("\n*** Please enter a valid option! ***\n");
            continue;
        }

        char trashData;
        int parsed = sscanf(buffer, "%d %c", &selection, &trashData);

        if (parsed != 1)
        {
            printf("\n*** Please enter a valid option! ***\n");
            continue;
        }

        switch (selection)
        {
        case 1:
            createRecordMenu(&table);
            break;

        case 2:
            editRecordMenu(&table);
            break;

        case 3:
            deleteRecordMenu(&table);
            break;

        case 4:
            searchRecordMenu(&table);
            break;

        case 5:
            listRecordMenu(&table);
            break;

        case 6:
            break;

        default:
            printf("\n*** Please enter a valid option! ***\n");
            break;
        }
    }

    destroyRecordTable(&table);
}

We’ll get into destroyRecordTable as well as other clean up functions in the next article, so don’t worry about that for now.

As we can see, we have 6 options to choose from:

  • Create Record Menu
  • Edit Record Menu
  • Delete Record Menu
  • Search Record Menu
  • List Record Menu
  • Exit

Let’s go down the line starting with createRecordMenu.

Create Record Menu

The create record menu is one of the more simple menus that we’ll design since most of it is simply input validation. As you can see in createRecordMenu we’re not allowing users to have first and last names longer than 100 characters, which saves us a lot of headache in our createRecord function which is called at the very end of all this input validation.

static void createRecordMenu(struct RecordTable *table)
{
    char buffer[200];

    clearScreen();

    printf("===============================\n");
    printf("         CREATE RECORD         \n");
    printf("===============================\n");

    while (1)
    {
        char firstNameInput[101];
        char lastNameInput[101];
        int ageInput = -1;

        int gotFirstName = 0;
        int gotLastName = 0;
        int gotAge = 0;

        while (!gotFirstName)
        {
            printf("Enter First Name: ");

            if (fgets(buffer, sizeof(buffer), stdin) == NULL)
            {
                printf("\n*** Please enter a valid first name! ***\n");

                continue;
            }

            size_t inputLength = strlen(buffer);

            if (inputLength > 0 && buffer[inputLength - 1] == '\n')
            {
                buffer[inputLength - 1] = '\0';
                inputLength--;
            }
            else if (inputLength == sizeof(buffer) - 1)
            {
                int ch;

                while ((ch = getchar()) != '\n' && ch != EOF)
                {
                }

                printf("\n*** First name cannot be longer than 100 characters! ***\n");

                continue;
            }

            if (inputLength > 100)
            {
                printf("\n*** First name cannot be longer than 100 characters! ***\n");

                continue;
            }
            else if (inputLength == 0)
            {
                printf("\n*** First Name cannot be empty! ***\n");

                continue;
            }

            strcpy(firstNameInput, buffer);

            gotFirstName = 1;
        }

        while (!gotLastName)
        {
            printf("Enter Last Name: ");

            if (fgets(buffer, sizeof(buffer), stdin) == NULL)
            {
                printf("\n*** Please enter a valid last name! ***\n");
                continue;
            }

            size_t inputLength = strlen(buffer);

            if (inputLength > 0 && buffer[inputLength - 1] == '\n')
            {
                buffer[inputLength - 1] = '\0';
                inputLength--;
            }
            else if (inputLength == sizeof(buffer) - 1)
            {
                int ch;

                while ((ch = getchar()) != '\n' && ch != EOF)
                {
                }

                printf("\n*** Last name cannot be longer than 100 characters! ***\n");

                continue;
            }

            if (inputLength > 100)
            {
                printf("\n*** Last name cannot be longer than 100 characters! ***\n");

                continue;
            }
            else if (inputLength == 0)
            {
                printf("\n*** Last Name cannot be empty! ***\n");

                continue;
            }

            strcpy(lastNameInput, buffer);

            gotLastName = 1;
        }

        while (!gotAge)
        {
            printf("Enter Age: ");

            if (fgets(buffer, sizeof(buffer), stdin) == NULL)
            {
                printf("\n*** Please enter a valid age! ***\n");
                continue;
            }

            size_t inputLength = strlen(buffer);

            if (inputLength > 0 && buffer[inputLength - 1] == '\n')
            {
                buffer[inputLength - 1] = '\0';
            }
            else if (inputLength == sizeof(buffer) - 1)
            {
                int ch;

                while ((ch = getchar()) != '\n' && ch != EOF)
                {
                }

                printf("\n*** Please enter a valid age! ***\n");

                continue;
            }

            if (inputLength == 0)
            {
                printf("\n*** Age cannot be empty! ***\n");

                continue;
            }

            char trashData;
            int parsed = sscanf(buffer, "%d %c", &ageInput, &trashData);

            if (parsed != 1)
            {
                printf("\n*** Please enter a valid age! ***\n");

                continue;
            }

            if (ageInput < 0)
            {
                printf("\n*** Please enter a valid age! ***\n");

                continue;
            }

            gotAge = 1;
        }

        enum InsertResult recordInsertResult = createRecord(table, firstNameInput, lastNameInput, ageInput);

        if (recordInsertResult != INSERT_SUCCESS)
        {
            printf("*** Record could not be created. Exited with Insert Code: %d ***\n", recordInsertResult);

            break;
        }

        clearScreen();

        printf(">>> Record Created <<<\n");
        printf(">>> %s %s, %d <<<\n", firstNameInput, lastNameInput, ageInput);

        break;
    }
}

This is really all there is for the create record menu. Congratulations! Onto the edit record menu.

Edit Record Menu

The Edit Record Menu will actually consist of a 3 step process:

  1. Menu to enter the ID of the record we want to edit
  2. If the record is found, show menu for user to select which column they want to edit
  3. Edit menu for the selected column

As we mentioned earlier, when it comes to editing or deleting, the user must have the ID of the record they’d like to edit. We’re not going to get more complicated than that for the sake of this project. Let’s create the menu that requires the user to enter a record ID.

static void editRecordMenu(struct RecordTable *table)
{
    char buffer[100];
    int input = 0;

    clearScreen();

    while (1)
    {
        printf("===============================\n");
        printf("          EDIT RECORDS         \n");
        printf("===============================\n");

        printf("/// NOTE: Enter -1 to return to the Main Menu ///\n");
        printf("Enter the ID of the record you want to edit: ");

        if (fgets(buffer, sizeof(buffer), stdin) == NULL)
        {
            printf("\n*** Please enter a valid ID! ***\n");
            continue;
        }

        char trashData;
        int parsed = sscanf(buffer, "%d %c", &input, &trashData);

        if (parsed != 1)
        {
            printf("\n*** Please enter a valid ID! ***\n");
            continue;
        }

        if (input < -1)
        {
            printf("\n*** Please enter a valid ID! ***\n");
            continue;
        }
        else if (input == -1)
        {
            clearScreen();
            break;
        }

        struct SearchResults results = {0};

        enum SearchResultStatus searchStatus = searchById(table, input, &results);

        if (searchStatus != SEARCH_SUCCESS)
        {
            clearScreen();

            printf("\n*** An error occurred when searching for the record! ***\n");

            break;
        }

        if (results.count == 0)
        {
            printf("\n>>> No records found for ID %d <<<\n", input);

            continue;
        }

        enum EditResult editResult = editFoundRecordMenu(table, &results);

        freeSearchResults(&results);

        if (editResult != EDIT_SUCCESS)
        {
            continue;
        }

        clearScreen();

        printf("\n>>> Record Successfully Edited! <<<\n");

        break;
    }
}

Notice that we’re freeing the search results using freeSearchResults. We set up this function in part 5 and we’ll be using it here as well as in a few other functions.

Once the user enters a valid ID, they’ll be taken to the menu that allows them to select which column they want to edit.

static enum EditResult editFoundRecordMenu(struct RecordTable *table, const struct SearchResults *results)
{
    char buffer[100];
    int input = 0;

    clearScreen();

    while (1)
    {
        printf(">>> Found Record <<<<\n");
        printf("ID: %d\n", results->records[0]->id);
        printf("First Name: %s\n", results->records[0]->firstName);
        printf("Last Name: %s\n", results->records[0]->lastName);
        printf("Age: %d\n\n", results->records[0]->age);

        printf("Select the column you wish to edit:\n");
        printf("1. First Name\n");
        printf("2. Last Name\n");
        printf("3. Age\n");
        printf("------\n");
        printf("4. Back\n\n\n");

        if (fgets(buffer, sizeof(buffer), stdin) == NULL)
        {
            printf("\n*** Please enter a valid input! ***\n");
            continue;
        }

        char trashData;
        int parsed = sscanf(buffer, "%d %c", &input, &trashData);

        if (parsed != 1)
        {
            printf("\n*** Please enter a valid input! ***\n");
            continue;
        }

        enum EditResult editResult;

        switch (input)
        {
        case 1:
            editResult = editFirstNameMenu(table, results);
            break;

        case 2:
            editResult = editLastNameMenu(table, results);
            break;

        case 3:
            editResult = editAgeMenu(table, results);
            break;

        case 4:
            clearScreen();
            return EDIT_CANCELED;

        default:
            printf("\n*** Please enter a valid input! ***\n");
            continue;
        }

        if (editResult != EDIT_SUCCESS)
        {
            clearScreen();

            printf("\n*** ERROR: EDIT RESULT RETURNED WITH CODE: %d ***\n", editResult);

            return editResult;
        }

        clearScreen();

        break;
    }

    return EDIT_SUCCESS;
}

We’ll continue with using the first name column as our example. The other columns are the same concept and are not changed substantially.

static enum EditResult editFirstNameMenu(struct RecordTable *table, const struct SearchResults *results)
{
    char buffer[200];

    clearScreen();

    printf("/// OLD FIRST NAME: %s ///\n", results->records[0]->firstName);

    while (1)
    {
        printf("Enter new First Name: ");

        if (fgets(buffer, sizeof(buffer), stdin) == NULL)
        {
            printf("\n*** Please enter a valid first name! ***\n");

            continue;
        }

        size_t inputLength = strlen(buffer);

        if (inputLength > 0 && buffer[inputLength - 1] == '\n')
        {
            buffer[inputLength - 1] = '\0';
            inputLength--;
        }
        else if (inputLength == sizeof(buffer) - 1)
        {
            int ch;

            while ((ch = getchar()) != '\n' && ch != EOF)
            {
            }

            printf("\n*** First name cannot be longer than 100 characters! ***\n");

            continue;
        }

        if (inputLength > 100)
        {
            printf("\n*** First name cannot be longer than 100 characters! ***\n");

            continue;
        }
        else if (inputLength == 0)
        {
            printf("\n*** First Name cannot be empty! ***\n");


            continue;
        }

        enum EditResult editResult = editFirstName(table, results->records[0]->id, buffer);

        if (editResult != EDIT_SUCCESS)
        {
            return editResult;
        }

        break;
    }

    return EDIT_SUCCESS;
}

Hopefully we’re beginning to see by now how all of the pieces are connecting to one another. In my opinion, practical implementations like this help me understand abstract concepts way better than simply telling me something like, “and this is how it works.” If you’d like to check out the last name and age edit menus, take a look at the project listed at the top of this article.

Delete Record Menu

This menu will also request the ID of the record the user wishes to delete. In this function, we’re searching for the ID first just like we did with the above edit function.

static void deleteRecordMenu(struct RecordTable *table)
{
    char buffer[100];
    int input = 0;

    clearScreen();

    while (1)
    {
        printf("===============================\n");
        printf("         DELETE RECORDS        \n");
        printf("===============================\n");

        printf("/// NOTE: Enter -1 to return to the Main Menu ///\n");
        printf("Enter the ID of the record you want to delete: ");

        if (fgets(buffer, sizeof(buffer), stdin) == NULL)
        {
            printf("\n*** Please enter a valid ID! ***\n");
            continue;
        }

        char trashData;
        int parsed = sscanf(buffer, "%d %c", &input, &trashData);

        if (parsed != 1)
        {
            printf("\n*** Please enter a valid ID! ***\n");
            continue;
        }

        if (input < -1)
        {
            printf("\n*** Please enter a valid ID! ***\n");
            continue;
        }
        else if (input == -1)
        {
            clearScreen();
            break;
        }

        struct SearchResults results = {0};

        enum SearchResultStatus searchStatus = searchById(table, input, &results);

        if (searchStatus != SEARCH_SUCCESS)
        {
            clearScreen();

            printf("\n*** An error occurred when searching for the record! ***\n");

            break;
        }

        if (results.count == 0)
        {
            printf("\n>>> No records found for ID %d <<<\n", input);

            continue;
        }

        enum DeleteResult deleteResult = confirmDelete(table, &results);

        freeSearchResults(&results);

        if (deleteResult != DELETE_SUCCESS)
        {
            continue;
        }

        clearScreen();

        printf("\n>>> Record Successfully Deleted! <<<\n");

        break;
    }
}

After a valid ID is given, we'll then ask the user to confirm. Then, we'll delete the record.

static enum DeleteResult confirmDelete(struct RecordTable *table, const struct SearchResults *results)
{
    char buffer[100];
    int input = 0;

    clearScreen();

    while (1)
    {
        printf(">>> Found Record <<<<\n");
        printf("ID: %d\n", results->records[0]->id);
        printf("First Name: %s\n", results->records[0]->firstName);
        printf("Last Name: %s\n", results->records[0]->lastName);
        printf("Age: %d\n\n", results->records[0]->age);

        printf("Are you sure you want to delete this record?\n");
        printf("1. Yes\n");
        printf("2. No\n");

        if (fgets(buffer, sizeof(buffer), stdin) == NULL)
        {
            printf("\n*** Please enter a valid input! ***\n");
            continue;
        }

        char trashData;
        int parsed = sscanf(buffer, "%d %c", &input, &trashData);

        if (parsed != 1)
        {
            printf("\n*** Please enter a valid input! ***\n");
            continue;
        }

        if (input < 1 || input > 2)
        {
            printf("\n*** Please enter a valid input! ***\n");
            continue;
        }
        else if (input == 2)
        {
            clearScreen();

            return DELETE_CANCELED;
        }

        enum DeleteResult deleteResult = deleteRecord(table, results->records[0]->id);

        if (deleteResult != DELETE_SUCCESS)
        {
            clearScreen();

            printf("\n*** ERROR: DELETE RESULT RETURNED WITH CODE: %d ***\n", deleteResult);

            return deleteResult;
        }

        clearScreen();

        break;
    }

    return DELETE_SUCCESS;
}

That handles deleting records. Now onto searching for records which is similar to the edit menus, except we're not modifying any of the records. Go figure.

Search Record Menu

Back in parts 5 and 6, we set ourselves up early for the console UI which is going to make these menus pretty easy to implement. In fact, the only thing we're doing is input validation. You might have noticed that this is all this code is that you've seen in this article and you're right! This is one of the most important parts of any program after all.

Anyways, let's get the search menu created.

static void searchRecordMenu(const struct RecordTable *table)
{
    char buffer[100];
    int selection = 0;

    clearScreen();

    while (selection != 5)
    {
        printf("===============================\n");
        printf("         SEARCH RECORDS        \n");
        printf("===============================\n");
        printf("1. Search by ID\n");
        printf("2. Search by First Name\n");
        printf("3. Search by Last Name\n");
        printf("4. Search by Age\n");
        printf("------\n");
        printf("5. Back to Main Menu\n\n\n");

        printf("Select an option: ");

        if (fgets(buffer, sizeof(buffer), stdin) == NULL)
        {
            printf("\n*** Please enter a valid option! ***\n");
            continue;
        }

        char trashData;
        int parsed = sscanf(buffer, "%d %c", &selection, &trashData);

        if (parsed != 1)
        {
            printf("\n*** Please enter a valid option! ***\n");
            continue;
        }

        switch (selection)
        {
        case 1:
            searchIdMenu(table);
            break;

        case 2:
            searchFirstNameMenu(table);
            break;

        case 3:
            searchLastNameMenu(table);
            break;

        case 4:
            searchAgeMenu(table);
            break;

        case 5:
            clearScreen();
            break;

        default:
            printf("\n*** Please enter a valid option! ***\n");
            break;
        }
    }
}

Like before, we'll use our first name column as our example and look who decided to show up: our trusty friend printSearchRecords which is already done for us.

static void searchFirstNameMenu(const struct RecordTable *table)
{
    char buffer[200];

    clearScreen();

    while (1)
    {
        printf("Enter First Name to search: ");

        if (fgets(buffer, sizeof(buffer), stdin) == NULL)
        {
            printf("\n*** Please enter a valid first name! ***\n");

            continue;
        }

        size_t inputLength = strlen(buffer);

        if (inputLength > 0 && buffer[inputLength - 1] == '\n')
        {
            buffer[inputLength - 1] = '\0';
            inputLength--;
        }
        else if (inputLength == sizeof(buffer) - 1)
        {
            int ch;

            while ((ch = getchar()) != '\n' && ch != EOF)
            {
            }

            printf("\n*** First name cannot be longer than 100 characters! ***\n");

            continue;
        }

        if (inputLength > 100)
        {
            printf("\n*** First name cannot be longer than 100 characters! ***\n");

            continue;
        }
        else if (inputLength == 0)
        {
            printf("\n*** First Name cannot be empty! ***\n");

            continue;
        }

        struct Record temp;

        strcpy(temp.firstName, buffer);

        printSearchRecords(table, SEARCH_FIRSTNAME, temp);

        break;
    }
}

Alright, we're almost done. Just the list records menu left and we can wrap up our program!

List Records Menu

At this point, I think you get the picture. Get user input, validate, and print to screen. We already have printListRecords from before, so that's all we need to call in our switch statement.

static void listRecordMenu(const struct RecordTable *table)
{
    char buffer[100];
    int selection = 0;

    clearScreen();

    while (selection != 5)
    {
        printf("===============================\n");
        printf("          LIST RECORDS         \n");
        printf("===============================\n");
        printf("1. List by ID\n");
        printf("2. List by First Name\n");
        printf("3. List by Last Name\n");
        printf("4. List by Age\n");
        printf("------\n");
        printf("5. Back to Main Menu\n\n\n");

        printf("Select an option: ");

        if (fgets(buffer, sizeof(buffer), stdin) == NULL)
        {
            printf("\n*** Please enter a valid option! ***\n");
            continue;
        }

        char trashData;
        int parsed = sscanf(buffer, "%d %c", &selection, &trashData);

        if (parsed != 1)
        {
            printf("\n*** Please enter a valid option! ***\n");
            continue;
        }

        switch (selection)
        {
        case 1:
            printListRecords(table, SORT_ID);
            break;

        case 2:
            printListRecords(table, SORT_FIRSTNAME);
            break;

        case 3:
            printListRecords(table, SORT_LASTNAME);
            break;

        case 4:
            printListRecords(table, SORT_AGE);
            break;

        case 5:
            clearScreen();
            break;

        default:
            printf("\n*** Please enter a valid option! ***\n");
            break;
        }
    }
}

Just like that we're finished with our console UI! Let's begin to wrap up this program.

← Back to Part 7 Continue to Part 9 →