Kimu foods

Kimu foods website

This will be a brief overview of Kimu Foods, a recipe-based grocery delivery app. I will mostly go over the current architecture as well as my plans for it.

Overview

Kimu Foods is built using three distinct technologies that serve different purposes. For the backend, I decided to use a GraphQL Apollo Server with Node.js. The web client is built with Next.js, though I am considering switching to Svelte. The mobile client is developed using Flutter.

Backend

For the backend, I tried three different tools to implement a GraphQL API. I began with Go, and with the help of the gqlgen library, I was able to spin up a server that returned GraphQL responses.

package main

import (
 "bytes"
 "io"
 "log"
 "net/http"
 "os"

 "kimu_backend/cmd/app/resolvers"
 "kimu_backend/config"
 "kimu_backend/graph"

 "github.com/99designs/gqlgen/graphql/handler"
 "github.com/99designs/gqlgen/graphql/playground"
 _ "github.com/lib/pq"
)

const defaultPort = "4000"

func main() {
 port := os.Getenv("PORT")
 if port == "" {
  port = defaultPort
 }

 db, err := config.ConnectDB()
 if err != nil {
  log.Fatal(err)
 }
 defer db.Close()

 resolver := &resolvers.Resolver{DB: db}
 srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: resolver}))

 http.Handle("/playground", playground.Handler("GraphQL playground", "/query"))
 http.Handle("/", logRequestsMiddleware(srv))

 log.Printf("server running on http://localhost:%s/ 🚀🚀", port)
 log.Fatal(http.ListenAndServe(":"+port, nil))
}

func logRequestsMiddleware(next http.Handler) http.Handler {
 // Open or create the log file
 logFile, err := os.OpenFile("logs/requests.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
 if err != nil {
  log.Fatalf("Failed to open log file: %v", err)
 }
 // Create a new logger that writes to the file
 logger := log.New(logFile, "", log.LstdFlags)

 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  // Log the request method and URL
  logger.Printf("Request: %s %s", r.Method, r.URL.Path)

  // Read and log the request body
  if r.Body != nil {
   body, err := io.ReadAll(r.Body)
   if err == nil {
    logger.Printf("Request Body: %s", string(body))
    // Replace the body so the handler can still read it
    r.Body = io.NopCloser(bytes.NewReader(body))
   } else {
    logger.Printf("Error reading body: %v", err)
   }
  }

  // Call the next handler
  next.ServeHTTP(w, r)
 })
}

Next, I tried using Ktor with Kotlin

package ke.kimu

import io.ktor.server.application.*
import io.ktor.server.routing.routing

fun main(args: Array<String>) {
    io.ktor.server.netty.EngineMain.main(args)
}

fun Application.module() {
    install(GraphQL) {
        schema {
            packages = listOf("ke.kimu")
            queries = listOf(
                KimuQuery()
            )
        }
    }
    routing {
        graphQLPostRoute()
        graphiQLRoute()
        graphQLSDLRoute()
    }
}

class KimuQuery : Query {
    fun kimuTest(): KimuTest = KimuTest("Kimu test", "here here")
}

data class KimuTest(val name: String, val location: String)

I enjoyed using both languages/tools, but the main issues I faced were likely due to my lack of familiarity with the technologies. Starting with Go, I ran into difficulties when implementing complex queries and mutations. Since I had only beginner-level experience, my problem-solving skills in this context weren’t strong. Another challenge that threw me off was having to write SQL queries for my resolvers, as I didn’t see this as a viable long-term solution.

// Recipes is the resolver for the recipes field.
func (r *queryResolver) Recipes(ctx context.Context) ([]*models.Recipe, error) {
 rows, err := r.DB.Query(`SELECT id, "recipeName", "categoryName", duration, people, description, amount, "imageUrl", instructions FROM "Recipe"`)
 if err != nil {
  return nil, err
 }
 defer rows.Close()

For Ktor and Kotlin, my inexperience again became a challenge, as I wasn’t able to efficiently implement my ideas. Another frustration was working with Exposed to manipulate my database I had issues getting it to work with GraphQL. I will probably build another project using Ktor, as I really like the tool.

Apollo Node.js

I decided to work with Apollo and Node.js for my backend, using a PostgreSQL database with Prisma as the ORM.

image.png

I chose a code-first approach with Nexus over a schema-first one, as it provided a simpler way to keep my types in sync from Prisma through to my queries and mutations.

Web client

The web client is a Next.js app. Currently, it only has a landing page, but my goal is to add the admin functionality here.

Mobile client

The mobile client is built with Flutter, as I wanted a cross-platform application right out of the box. Here are a couple of screens from the app.

sign_in.pngsign_up.png
home_screen.pnghome_screen_scrolled.png
recipe_details.pngrecipe_details_scrolled.png
categories.pngcategories_scrolled.png
favourites.pngorders.png
profile.pngprofile_scrolled.png

Conclusion

This article and application are still works in progress, and I will update the article as I continue development.

View more