Building a Bodega with SQL and TypeScript

Introduction

Let's do some roleplay and put on our pretend to be a consultant who helps build software solutions. In our hypothetical scenario, we'll have a mom and pop who own a bodega in the neighborhood. They may not be the most technologically savy, but they know how to keep records, and more importantly, they know how to run a shop. So their current paper and Excel-based inventory management system works, why fix it?

Well business is booming and they, mom and pop decide, they can probably open up a couple more bodegas around town, after all, they know how to run a shop. Unforntuately, here's where problems start coming up, they quickly realize that manual operations are fine for one store, maybe two, but after a couple, things start getting hard when you have to order shipments and keep the shelves stocked.

Luckily for them, their friendly neighborhood software engineer is here to help them build a more efficient and user-friendly software solution. This new solution will leverage databases, web development, and web APIs to provide them with an accessible and maintainable way to manage their inventory.

Our goal is to build an inventory system using SQL, TypeScript, an Object-Relational Mapping (ORM) library, and a web framework. The system will be accessible via a web API, making it easy for the owners to interact with the data and keep their inventory up-to-date.

Here's an outline of the steps we will go through to build this solution:

  1. Analyzing the problem and gathering requirements
  2. Designing the database schema
  3. Setting up the project environment and installing dependencies
  4. Implementing the backend using TypeScript, an ORM, and a web framework
  5. Testing the API and verifying functionality
  6. Deploying the solution to a server
  7. Providing documentation for the mom and pop to use the new system

By the end of this tutorial, we will have built a modern and scalable inventory management system that will help the bodega owners save time and effort, allowing them to focus on growing their business.

1. Analyzing the Problem and Gathering Requirements

Before diving into the development process, it's crucial to analyze the problem and gather the necessary requirements. This step helps us understand the specific needs of the owners and ensures that our solution will be tailored to their unique situation.

Understanding the Business Context

First, let's get familiar with the business context. We know that our clients own a couple of bodegas in the neighborhood, and they currently track their inventory using paper and Excel spreadsheets. They are looking for a more efficient and user-friendly solution to help them manage their inventory.

Identifying the Users

Next, we need to identify the users of the system. In this case, our primary users are the bodega owners, who will be responsible for managing the inventory. They need a simple and intuitive interface to add, update, and delete inventory items. They might also want to delegate some of these tasks to their employees, so it's essential to consider different user roles and access levels.

Defining the Functional Requirements

Once we understand the users and the business context, we can start defining the functional requirements. These are the features and capabilities that the system must have to meet the needs of the bodega owners. Some of the functional requirements for our inventory system include:

  1. The ability to add, update, and delete products, including their names, descriptions, prices, and quantities.
  2. The ability to track inventory across multiple bodegas.
  3. Support for different user roles and access levels, such as owners and employees.
  4. The ability to generate inventory reports, showing the current stock levels and sales trends.
  5. A search functionality to quickly find products in the inventory.
  6. The system should be accessible through a web API, allowing for future integration with other tools or platforms.

Defining the Non-functional Requirements

Non-functional requirements are the characteristics of the system that affect its quality, such as performance, security, and usability. Some non-functional requirements to consider for our inventory system include:

  1. The system should be easy to use and require minimal training for the bodega owners and their employees.
  2. The system should be scalable, allowing the bodega owners to add more stores or products without significant performance issues.
  3. The system should be secure, protecting sensitive data from unauthorized access.
  4. The system should be maintainable, making it easy to update and fix issues as needed.

By carefully analyzing the problem and gathering requirements, we can ensure that our solution will meet the needs of the bodega owners, providing them with a modern and efficient inventory management system. With a clear understanding of the problem and requirements, we can move on to designing the database schema and implementing the solution.

2. Designing the Database Schema

Before we dive into writing code for our bodega inventory system, it's crucial to design the database schema. A well-designed schema will ensure that our data is organized, consistent, and easily accessible. In this section, we'll outline the schema for our inventory system, considering the different entities and their relationships.

1. Identifying the Entities

Let's start by identifying the primary entities in our inventory system:

  • Product: Represents an individual product sold in the bodega. It has attributes such as name, description, and price.
  • Store: Represents a physical store location. It has attributes like name, address, and contact information.
  • Inventory: Represents the stock of a particular product at a specific store location. It has attributes such as quantity, reorder level, and reorder quantity.

2. Defining the Relationships

Next, we need to define the relationships between our entities:

  • A Store can have multiple Inventory records, but each Inventory record belongs to only one Store. This is a one-to-many relationship.
  • A Product can have multiple Inventory records across different stores, but each Inventory record is associated with only one Product. This is also a one-to-many relationship.

3. Setting up the Project Environment and Installing Dependencies

To build our inventory system for the owners, we will be using PostgreSQL as the database, TypeScript for the programming language, Prisma as the ORM (Object-Relational Mapping) library, and NestJS as the web framework. In this section, we will walk through setting up the project environment and installing the necessary dependencies.

1. Install Node.js and npm

Before we begin, make sure you have Node.js and npm (Node Package Manager) installed on your machine. You can download the latest version of Node.js from the official website: https://nodejs.org/. Npm comes bundled with Node.js, so you don't need to install it separately.

2. Install the NestJS CLI

We will use the NestJS CLI (Command Line Interface) to create and manage our project. Install the CLI globally on your machine by running the following command:

npm install -g @nestjs/cli

3. Create a New NestJS Project

Now that you have the NestJS CLI installed, create a new NestJS project by running the following command:

nest new bodega-inventory-system

This command will prompt you for some project configuration options. Choose "TypeScript" as the language, and accept the default options for the rest. After the project is created, navigate to the project directory:

cd bodega-inventory-system

4. Install PostgreSQL (via Docker)

If you haven't already, install Docker on your machine. You can download the installer for your operating system from the official Docker website: https://docs.docker.com/get-docker/.

After installing Docker, ensure it's running on your machine. You can check its status by running the following command in your terminal or command prompt:

docker --version

Run PostgreSQL in a Docker container

Now that Docker is installed and running, you can create a new PostgreSQL container for the inventory system. To do this, run the following command:

docker run --name inventory-postgres -e POSTGRES_PASSWORD=your_password -p 5432:5432 -d postgres

This command will:

  • Create a new container named inventory-postgres using the official PostgreSQL Docker image.
  • Set the PostgreSQL password to your_password (replace this with a strong password of your choice).
  • Expose port 5432 on your machine to connect to the PostgreSQL server.
  • Run the container in detached mode (d flag) so that it runs in the background.

After running this command, you should see a container ID as output, indicating that the container has been created and is running.

Next, you need to create a new PostgreSQL database for the inventory system. To do this, first connect to the running PostgreSQL container using the docker exec command:

docker exec -it inventory-postgres psql -U postgres

This will open the psql command-line interface. Now, create a new database by running the following SQL command:

CREATE DATABASE inventory;

Finally, note the connection details for your new PostgreSQL container (host, port, database name, username, and password). The host will be localhost, the port will be 5432, the database name will be inventory, the username will be postgres, and the password will be the one you set when creating the container (your_password).

5. Install Prisma and Set up the Database Connection

To install Prisma, run the following command in your project directory:

npm install @prisma/cli @prisma/client

Next, create a new Prisma configuration file by running:

npx prisma init

This command will generate a prisma folder containing a schema.prisma file. Open the schema.prisma file and update the datasource block to include your PostgreSQL connection details:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

Create a .env file in the project root directory and set the DATABASE_URL environment variable with your connection details:

DATABASE_URL="postgresql://username:password@localhost:5432/bodega_inventory_system?schema=public"

6. Install NestJS Prisma Integration and Other Dependencies

Install the NestJS Prisma integration package and other necessary dependencies by running:

npm install @nestjs-modules/prisma nestjs-typeorm-paginate

Now, your project environment is set up, and you have installed all the required dependencies. The next steps will involve designing the database schema, implementing the API endpoints, and setting up the front-end to interact with the inventory system.

4. Implementing the backend using TypeScript, an ORM, and a web framework

1. Setting up Prisma and Migrating the Database

Creating the Schema

Now that we have identified the entities and their relationships and have our environment set up, we can create the schema in the schema.prisma file:

model Product {
  id          Int      @id @default(autoincrement())
  name        String
  description String?
  price       Float
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
  inventories Inventory[]
}

model Store {
  id          Int      @id @default(autoincrement())
  name        String
  address     String
  contactInfo String?
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
  inventories Inventory[]
}

model Inventory {
  id            Int      @id @default(autoincrement())
  productId     Int
  storeId       Int
  quantity      Int
  reorderLevel  Int
  reorderQuantity Int
  createdAt     DateTime @default(now())
  updatedAt     DateTime @updatedAt
  product       Product  @relation(fields: [productId], references: [id])
  store         Store    @relation(fields: [storeId], references: [id])
}

In this schema, we've defined the Product, Store, and Inventory models with their respective attributes and relationships. The createdAt and updatedAt fields help track when a record was created or last updated.

Migrating the Database

Then, we'll create a migration to generate our database schema. Make sure you've configured your prisma/.env file to connect to your PostgreSQL database. Then, run the following command to create a migration:

npx prisma migrate dev --name init

This command will generate a migration folder with the SQL scripts necessary to create the schema in your database. It will also apply the migration to the database and generate the Prisma Client, which we'll use to interact with the database in our application.

2. Creating the Services

Next, we'll create the services to handle the business logic for our entities. These services will interact with the Prisma Client to perform CRUD operations on the database.

Run the following commands to generate the services:

nest generate service product
nest generate service store
nest generate service inventory

In each service file, import the PrismaService and use dependency injection to access it. Then, create the necessary methods for CRUD operations and any additional business logic needed.

3. Creating the Controllers

Now, let's create controllers to handle incoming HTTP requests and interact with our services.

Run the following commands to generate the controllers:

nest generate controller product
nest generate controller store
nest generate controller inventory

In each controller file, import the corresponding service and use dependency injection to access it. Then, create the appropriate HTTP endpoints (GET, POST, PUT, DELETE) and call the relevant methods from the services.

For example, the ProductController might look like this:

import {
  Body,
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Param,
  HttpStatus,
  HttpCode,
} from '@nestjs/common';
import { ProductService } from './product.service';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';

@Controller('products')
export class ProductController {
  constructor(private readonly productService: ProductService) {}

  @Get()
  async findAll() {
    return this.productService.findAll();
  }

  @Get(':id')
  async findOne(@Param('id') id: number) {
    return this.productService.findOne(id);
  }

  @Post()
  @HttpCode(HttpStatus.CREATED)
  async create(@Body() createProductDto: CreateProductDto) {
    return this.productService.create(createProductDto);
  }

  @Put(':id')
  async update(
    @Param('id') id: number,
    @Body() updateProductDto: UpdateProductDto,
  ) {
    return this.productService.update(id, updateProductDto);
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  async delete(@Param('id') id: number) {
    await this.productService.delete(id);
  }
}

This implementation defines the following endpoints:

  1. GET /products: Retrieve all products
  2. GET /products/:id: Retrieve a single product by its id
  3. POST /products: Create a new product
  4. PUT /products/:id: Update an existing product by its id
  5. DELETE /products/:id: Delete a product by its id

For the create and update methods, we use Data Transfer Objects (DTOs) to define the expected structure of the incoming request body. Make sure to create the CreateProductDto and UpdateProductDto classes inside the dto folder within the product folder.

Here's an example of what those DTO classes could look like:

// create-product.dto.ts
export class CreateProductDto {
  name: string;
  description: string;
  price: number;
}

// update-product.dto.ts
export class UpdateProductDto {
  name?: string;
  description?: string;
  price?: number;
}

Here's an example implementation of the StoreController class that provides endpoints for creating, retrieving, updating, and deleting stores.

import {
  Body,
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Param,
  HttpStatus,
  HttpCode,
} from '@nestjs/common';
import { StoreService } from './store.service';
import { CreateStoreDto } from './dto/create-store.dto';
import { UpdateStoreDto } from './dto/update-store.dto';

@Controller('stores')
export class StoreController {
  constructor(private readonly storeService: StoreService) {}

  @Get()
  async findAll() {
    return this.storeService.findAll();
  }

  @Get(':id')
  async findOne(@Param('id') id: number) {
    return this.storeService.findOne(id);
  }

  @Post()
  @HttpCode(HttpStatus.CREATED)
  async create(@Body() createStoreDto: CreateStoreDto) {
    return this.storeService.create(createStoreDto);
  }

  @Put(':id')
  async update(
    @Param('id') id: number,
    @Body() updateStoreDto: UpdateStoreDto,
  ) {
    return this.storeService.update(id, updateStoreDto);
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  async delete(@Param('id') id: number) {
    await this.storeService.delete(id);
  }
}

This implementation defines the following endpoints:

  1. GET /stores: Retrieve all stores
  2. GET /stores/:id: Retrieve a single store by its id
  3. POST /stores: Create a new store
  4. PUT /stores/:id: Update an existing store by its id
  5. DELETE /stores/:id: Delete a store by its id

For the create and update methods, we use Data Transfer Objects (DTOs) to define the expected structure of the incoming request body. Make sure to create the CreateStoreDto and UpdateStoreDto classes inside the dto folder within the store folder.

Here's an example of what those DTO classes could look like:

// create-store.dto.ts
export class CreateStoreDto {
  name: string;
  address: string;
}

// update-store.dto.ts
export class UpdateStoreDto {
  name?: string;
  address?: string;
}

With this implementation, the StoreController class provides a full set of CRUD operations for managing stores in the bodega inventory system.

Here's an example implementation of the InventoryController class that provides endpoints for creating, retrieving, updating, and deleting inventory items

import {
  Body,
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Param,
  HttpStatus,
  HttpCode,
} from '@nestjs/common';
import { InventoryService } from './inventory.service';
import { CreateInventoryDto } from './dto/create-inventory.dto';
import { UpdateInventoryDto } from './dto/update-inventory.dto';

@Controller('inventory')
export class InventoryController {
  constructor(private readonly inventoryService: InventoryService) {}

  @Get()
  async findAll() {
    return this.inventoryService.findAll();
  }

  @Get(':id')
  async findOne(@Param('id') id: number) {
    return this.inventoryService.findOne(id);
  }

  @Post()
  @HttpCode(HttpStatus.CREATED)
  async create(@Body() createInventoryDto: CreateInventoryDto) {
    return this.inventoryService.create(createInventoryDto);
  }

  @Put(':id')
  async update(
    @Param('id') id: number,
    @Body() updateInventoryDto: UpdateInventoryDto,
  ) {
    return this.inventoryService.update(id, updateInventoryDto);
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  async delete(@Param('id') id: number) {
    await this.inventoryService.delete(id);
  }
}

This implementation defines the following endpoints:

  1. GET /inventory: Retrieve all inventory items
  2. GET /inventory/:id: Retrieve a single inventory item by its id
  3. POST /inventory: Create a new inventory item
  4. PUT /inventory/:id: Update an existing inventory item by its id
  5. DELETE /inventory/:id: Delete an inventory item by its id

For the create and update methods, we use Data Transfer Objects (DTOs) to define the expected structure of the incoming request body. Make sure to create the CreateInventoryDto and UpdateInventoryDto classes inside the dto folder within the inventory folder.

Here's an example of what those DTO classes could look like:

// create-inventory.dto.ts
export class CreateInventoryDto {
  productId: number;
  storeId: number;
  quantity: number;
}

// update-inventory.dto.ts
export class UpdateInventoryDto {
  productId?: number;
  storeId?: number;
  quantity?: number;
}

With this implementation, the InventoryController class provides a full set of CRUD operations for managing inventory items in the bodega inventory system.

4. Configuring the Application Module

Lastly, we need to configure our AppModule to import the necessary modules and register the services and controllers.

Update the app.module.ts file to import the PrismaModule, register the services, and include the controllers:

import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';
import { ProductController } from './product/product.controller';
import { StoreController } from './store/store.controller';
import { InventoryController } from './inventory/inventory.controller';
import { ProductService } from './product/product.service';
import { StoreService } from './store/store.service';
import { InventoryService } from './inventory/inventory.service';

@Module({
  imports: [PrismaModule],
  controllers: [ProductController, StoreController, InventoryController],
  providers: [ProductService, StoreService, InventoryService],
})
export class AppModule {}

With the backend implemented, we can now run our NestJS server and start using our API endpoints to interact with the bodega inventory system. In the next section, we'll discuss how to create a front-end to consume our API and provide a user interface for the owners.

4b. Recap without the code

To recap, in the previous sections, we created the ProductController, StoreController, and InventoryController classes. Let's review the API endpoints provided by these controllers.

  1. Product API Endpoints

In the previous section, we implemented the ProductController class, which provides the following API endpoints for managing products:

  • GET /products: Retrieve all products
  • GET /products/:id: Retrieve a single product by its id
  • POST /products: Create a new product
  • PUT /products/:id: Update an existing product by its id
  • DELETE /products/:id: Delete a product by its id
  1. Store API Endpoints

Similarly, we implemented the StoreController class, which provides the following API endpoints for managing stores:

  • GET /stores: Retrieve all stores
  • GET /stores/:id: Retrieve a single store by its id
  • POST /stores: Create a new store
  • PUT /stores/:id: Update an existing store by its id
  • DELETE /stores/:id: Delete a store by its id
  1. Inventory API Endpoints

Lastly, we created the InventoryController class, which provides the following API endpoints for managing inventory items:

  • GET /inventory: Retrieve all inventory items
  • GET /inventory/:id: Retrieve a single inventory item by its id
  • POST /inventory: Create a new inventory item
  • PUT /inventory/:id: Update an existing inventory item by its id
  • DELETE /inventory/:id: Delete an inventory item by its id

These endpoints allow for the necessary CRUD operations to manage the bodega inventory system effectively. With these API endpoints in place, the mom-and-pop owners can now access and manage their inventory data through a web API.

To test these endpoints, you can use tools like Postman or curl. In a production environment, you'd typically create a frontend application that communicates with these API endpoints to provide a user-friendly interface for managing the inventory data.

4c. Recap diving into some code

Let's take a deeper look at the ProductController and break down the process behind the scenes.

import { Controller, Get, Post, Put, Delete, Body, Param } from '@nestjs/common';
import { ProductService } from './product.service';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';

@Controller('products')
export class ProductController {
  constructor(private readonly productService: ProductService) {}

  @Get()
  async findAll() {
    return this.productService.findAll();
  }

  @Get(':id')
  async findOne(@Param('id') id: string) {
    return this.productService.findOne(id);
  }

  @Post()
  async create(@Body() createProductDto: CreateProductDto) {
    return this.productService.create(createProductDto);
  }

  @Put(':id')
  async update(
    @Param('id') id: string,
    @Body() updateProductDto: UpdateProductDto,
  ) {
    return this.productService.update(id, updateProductDto);
  }

  @Delete(':id')
  async remove(@Param('id') id: string) {
    return this.productService.remove(id);
  }
}

Here's a step-by-step explanation of what's happening in the ProductController:

  1. Importing necessary dependencies: At the beginning of the file, we import the required classes and decorators from the NestJS framework and the Prisma ORM. The Controller, Get, Post, Put, Delete, Body, and Param decorators are imported from the @nestjs/common package. We also import the ProductService, CreateProductDto, and UpdateProductDto classes, which are used to handle the business logic and data validation.
  2. Creating the ProductController class: We define a class called ProductController and use the @Controller decorator to indicate that this class is a NestJS controller. The decorator takes a string as an argument, which specifies the base path for the routes in this controller ('products' in this case).
  3. Injecting the ProductService: The ProductController class has a constructor that takes an instance of the ProductService class as a parameter. This service class is responsible for handling the business logic related to products. NestJS automatically handles dependency injection, so we don't need to instantiate the ProductService manually.
  4. Defining route handlers: Inside the ProductController class, we define several route handler methods that correspond to different HTTP requests:
    • findAll(): Handles the GET /products request. It calls the findAll() method of the ProductService class to retrieve all products from the database.
    • findOne(): Handles the GET /products/:id request. It takes a route parameter called id and calls the findOne() method of the ProductService class to retrieve a single product from the database based on its id.
    • create(): Handles the POST /products request. It takes a request body and validates it using the CreateProductDto class. Then, it calls the create() method of the ProductService class to create a new product in the database.
    • update(): Handles the PUT /products/:id request. It takes a route parameter called id and a request body, which is validated using the UpdateProductDto class. Then, it calls the update() method of the ProductService class to update an existing product in the database based on its id.
    • remove(): Handles the DELETE /products/:id request. It takes a route parameter called id and calls the remove() method of the ProductService class to delete a product from the database based on its id.

Each of these route handlers is decorated with the appropriate HTTP verb decorator (e.g., @Get, @Post, @Put, @Delete) to indicate the type of HTTP request it should handle. The decorators also take arguments that specify the route path, which can include route parameters like :id. NestJS handles the mapping of incoming HTTP requests to the corresponding route handlers based on the HTTP verb and the route path.

5. Manually Testing the API and verifying functionality

Now that we have implemented the API endpoints for managing our bodega inventory system, it's crucial to test the functionality and verify that everything works as expected. In this section, we will discuss how to test the API using a tool called Postman and run some test cases to validate the various CRUD operations.

Setting up Postman

Postman is a popular API development and testing tool that allows us to send HTTP requests and inspect responses easily. You can download and install Postman from their official website.

After installing Postman, open it and follow these steps to test the API:

  1. Create a new collection: Click on the "New" button in the top-left corner and select "Collection". Give your collection a name, like "Bodega Inventory System", and click "Create".
  2. Add requests to the collection: For each API endpoint, we will create a new request in the collection. Click on the "Add request" button within the collection, give it a meaningful name (e.g., "Get All Products"), and click "Save".

Running test cases

Now that we have set up Postman and our collection, let's run some test cases for each API endpoint.

Testing Products endpoints

  1. Get All Products: Select the "Get All Products" request in your collection. Set the HTTP method to "GET" and enter the URL http://localhost:3000/products. Click "Send" to make the request. You should receive a JSON array containing all the products in the database (initially, it should be empty).
  2. Create a Product: Select the "Create Product" request in your collection. Set the HTTP method to "POST" and enter the URL http://localhost:3000/products. In the "Body" tab, select "raw" and choose "JSON" as the content type. Enter a JSON object representing a new product (e.g., {"name": "Coca-Cola", "price": 1.5}). Click "Send" to make the request. You should receive a JSON object representing the created product, including its autogenerated id.
  3. Get a Single Product: Select the "Get Single Product" request in your collection. Set the HTTP method to "GET" and enter the URL http://localhost:3000/products/:id, replacing :id with the id of the product you just created. Click "Send" to make the request. You should receive a JSON object representing the requested product.
  4. Update a Product: Select the "Update Product" request in your collection. Set the HTTP method to "PUT" and enter the URL http://localhost:3000/products/:id, replacing :id with the id of the product you want to update. In the "Body" tab, select "raw" and choose "JSON" as the content type. Enter a JSON object containing the updated product data (e.g., {"name": "Coca-Cola", "price": 1.75}). Click "Send" to make the request. You should receive a JSON object representing the updated product.
  5. Delete a Product: Select the "Delete Product" request in your collection. Set the HTTP method to "DELETE" and enter the URL http://localhost:3000/products/:id, replacing :id with the id of the product you want to delete. Click "Send" to make the request. You should receive a JSON object representing the deleted product.

Repeat the same testing process for the Store and Inventory endpoints.

6. Deploying the solution to a server

After implementing and testing our bodega inventory system, the next step is to deploy the solution to a server, making it accessible to the store owners. In this section, we will discuss the process of deploying the application to a cloud provider using a platform like Heroku.

Preparing the application for deployment

Before deploying the application, we need to make some adjustments to the code and configuration:

  1. Environment variables: It's essential to store sensitive information, such as database credentials, in environment variables rather than hardcoding them in the application. Update your code to read these values from environment variables and create a .env file for local development.
  2. Procfile: Heroku uses a Procfile to determine how to start your application. Create a file named Procfile in the root of your project directory and add the following line: web: npm run start:prod. This command tells Heroku to run the production build of your application.
  3. Configure your database: Ensure that your database is accessible from the internet and has the necessary tables and relations set up. You may also want to create a separate user with limited permissions for your application to use.

Setting up Heroku

If you haven't already, sign up for a Heroku account and install the Heroku CLI. Then, follow these steps to deploy your application:

  1. Login to Heroku: Open a terminal and run heroku login. You'll be prompted to enter your Heroku credentials.
  2. Create a new Heroku app: Run heroku create to create a new Heroku app. This command will output the URL of your deployed application (e.g., https://your-app-name.herokuapp.com/).
  3. Configure environment variables: Set the required environment variables for your application using the heroku config:set command. For example: heroku config:set DATABASE_URL=your-database-url.

Deploying the application

Now that your application and Heroku are set up, you can deploy the application by following these steps:

  1. Initialize a Git repository: If your project isn't already using Git, run git init to initialize a new Git repository in your project directory.
  2. Commit your changes: Add all the files to the Git repository and commit the changes with a descriptive message, such as git add . followed by git commit -m "Initial commit".
  3. Add the Heroku remote: Run heroku git:remote -a your-app-name to add the Heroku remote to your Git repository. Replace your-app-name with the name of your Heroku app.
  4. Push your code to Heroku: Run git push heroku master to push your code to the Heroku remote. Heroku will automatically build and deploy your application.
  5. Open the deployed application: After the deployment is complete, you can run heroku open to open your deployed application in a web browser.

7. Providing documentation

Once you have built and deployed the bodega inventory system, it's essential to provide clear and concise documentation for the store owners, developers, and any other stakeholders who interact with the system. Good documentation ensures that users can understand and effectively use the system, while developers can maintain and extend it as needed. In this section, we'll discuss the key components of documentation for the inventory system, including user guides, API documentation, and code comments.

User Guides

User guides are essential for non-technical users, such as the store owners, who will be using the system to manage their inventory. The user guide should provide a comprehensive overview of the system's features, along with step-by-step instructions on how to perform common tasks, such as adding a new product, updating inventory levels, or generating reports.

When creating a user guide, consider the following:

  1. Use clear and concise language: Avoid technical jargon and write in a language that is easily understood by non-technical users.
  2. Include screenshots: Visual aids, such as annotated screenshots, can help users better understand the system's user interface and workflows.
  3. Organize the content: Break down the guide into sections or chapters based on different features or tasks, and use a clear hierarchy for headings and subheadings.
  4. Provide troubleshooting tips: Include a section on common issues and their solutions to help users solve problems they may encounter.

API Documentation

API documentation is crucial for developers who will interact with the system programmatically, such as integrating the inventory system with other software or building custom applications. API documentation should describe all available endpoints, their purpose, and the expected request and response formats.

Some best practices for creating API documentation include:

  1. Document each endpoint: Clearly describe the purpose of each endpoint, along with the required parameters, request body, response body, and any applicable error codes.
  2. Use examples: Provide example requests and responses to demonstrate how to interact with the API.
  3. Keep it up to date: Ensure that the documentation remains accurate as the API evolves, and promptly update it to reflect any changes or additions to the API.
  4. Consider using API documentation tools: Tools like Swagger, Postman, or Apiary can help generate interactive documentation that developers can explore and test directly.

Code Comments and Developer Documentation

Code comments and developer documentation are essential for any developers who will be maintaining or extending the inventory system. This documentation should provide an overview of the system's architecture, explain how different components interact, and offer guidance on best practices for making changes to the codebase.

When creating developer documentation, consider the following:

  1. Write descriptive code comments: Comment your code to explain the purpose of different sections, functions, and variables, as well as any complex logic or algorithms.
  2. Document design decisions: Explain the rationale behind important design decisions, such as the choice of database schema or the structure of the API.
  3. Include setup and deployment instructions: Provide clear instructions for setting up the development environment, running tests, and deploying the application to a server.
  4. Maintain a changelog: Keep a record of any changes made to the system, such as bug fixes, new features, or performance improvements, along with the date and the responsible developer.

By providing thorough documentation, you can ensure that the bodega inventory system is easy to use, maintain, and extend, helping the store owners streamline their inventory management and grow their business.

Bonus: Potential Next Steps

The inventory system we've built is just the beginning. There are numerous ways to expand the system, adding new features and functionality to enhance its usefulness and cater to the store owners' specific needs. In this bonus section, we'll explore some potential next steps you can take to expand on this solution and make it even more powerful.

1. Develop a User Interface (UI)

While the API provides a programmable way to interact with the inventory system, a user-friendly UI would make it more accessible for non-technical users, like the store owners. You could develop a web-based UI using popular frontend frameworks like React, Angular, or Vue.js. This UI would allow users to manage their inventory, stores, and products easily, without having to interact with the API directly.

2. Implement Authentication and Authorization

To ensure the security and privacy of the inventory data, you can add authentication and authorization features to the system. This would allow only authorized users to access the system, and grant specific permissions based on their roles (e.g., store owner, store manager, etc.). You could use authentication services like OAuth or integrate with third-party identity providers like Google or Facebook for a more streamlined user experience.

3. Integrate with External Services

You can expand the system's functionality by integrating it with external services, such as:

  • Payment processing: Integrate with payment gateways like Stripe or PayPal to enable in-app payments for products.
  • Shipping services: Connect with shipping providers like UPS, FedEx, or USPS to automatically generate shipping labels and track shipments.
  • Accounting software: Sync inventory data with popular accounting tools like QuickBooks or Xero to simplify financial management and reporting.

4. Add Inventory Forecasting and Analytics

By implementing inventory forecasting and analytics features, you can help the store owners make data-driven decisions regarding stock levels, purchasing, and sales. You could use historical data, seasonality, and trends to predict future inventory needs, and create visualizations to help users understand and interpret the data.

5. Support Multiple Locations and Warehouse Management

If the bodegas expand to multiple locations, you can enhance the system to support multiple store locations and centralized warehouse management. This would enable the store owners to efficiently manage inventory across different stores, track stock transfers between locations, and optimize stock levels based on regional demand.

6. Develop a Mobile App

A mobile app would allow store owners and staff to manage inventory on-the-go using their smartphones or tablets. You can create a mobile app for iOS and Android devices using cross-platform development frameworks like React Native or Flutter. The app could include features like barcode scanning for quick product lookups and updates, real-time inventory notifications, and offline access to critical data.

By taking these next steps, you can transform the inventory system into a comprehensive solution tailored to the unique needs of the bodegas, providing them with the tools they need to efficiently manage their inventory and grow their business.

Conclusion

In this post, we've walked through the process of building an inventory system for a mom and pop who own a few bodegas in their town. We've covered the essential steps needed to create a robust and user-friendly solution, from gathering requirements to deploying the final product. Here's a recap of the key sections we've covered:

  1. Analyzing the problem and gathering requirements: We started by understanding the needs of the store owners and defining the scope of the project. This helped us determine the necessary features and functionalities for the inventory system.
  2. Designing the database schema: We created a database schema to store and manage data related to products, stores, and inventory. This schema formed the foundation of our solution and allowed us to effectively organize and manage the information.
  3. Setting up the project environment and installing dependencies: We set up our development environment, selected the technology stack (PostgreSQL, TypeScript, Prisma, and NestJS), and installed the necessary dependencies to build the solution.
  4. Implementing the backend using TypeScript, an ORM, and a web framework: We built the backend of the inventory system using TypeScript, an ORM (Prisma), and a web framework (NestJS). This included implementing controllers, services, and models for products, stores, and inventory.
  5. Testing the API and verifying functionality: We tested our API endpoints using tools like Postman to ensure that our implementation was correct and that the inventory system functioned as expected.
  6. Deploying the solution to a server: We deployed our inventory system to a server, making it accessible to the store owners and enabling them to manage their inventory through the provided API.
  7. Providing documentation for the mom and pop to use the new system: We created clear and concise documentation that explained how to use the inventory system and its various features, empowering the store owners to easily adopt and benefit from the new solution.

In addition to these core steps, we also discussed potential next steps for expanding the inventory system, such as developing a user interface, adding authentication and authorization, integrating with external services, implementing inventory forecasting and analytics, supporting multiple locations, and creating a mobile app.

By following this process, we've built a comprehensive inventory system that enables the store owners to efficiently manage their bodegas, leveraging the power of databases, web development, and modern technology to replace their previous manual and spreadsheet-based methods.

Subscribe to rohp

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe