package main import ( "context" "database/sql" "flag" "fmt" "log" "net/http" "os" "time" "github.com/joho/godotenv" _ "github.com/lib/pq" ) // Declare a string containing the application version number. Later in the book we'll // generate this automatically at build time, but for now we'll just store the version // number as a hard-coded global constant. const version = "1.0.0" // Define a config struct to hold all the configuration settings for our application. // For now, the only configuration settings will be the network port that we want the // server to listen on, and the name of the current operating environment for the // application (development, staging, production, etc.). We will read in these // configuration settings from command-line flags when the application starts. type config struct { port int env string db struct { dsn string } } // Define an application struct to hold the dependencies for our HTTP handlers, helpers, // and middleware. At the moment this only contains a copy of the config struct and a // logger, but it will grow to include a lot more as our build progresses. type application struct { config config logger *log.Logger } func main() { logger := log.New(os.Stdout, "", log.Ldate|log.Ltime) var cfg config err := godotenv.Load() if err != nil { logger.Fatal(err) } dsn := os.Getenv("DATABASE_DSN") flag.IntVar(&cfg.port, "port", 4000, "API server port") flag.StringVar(&cfg.env, "env", "development", "Environment (development|staging|production)") // Read the DSN value from the db-dsn command-line flag into the config struct. We // default to using our development DSN if no flag is provided. flag.StringVar(&cfg.db.dsn, "db-dsn", dsn, "PostgreSQL DSN (From DATABASE_DSN in .env)") flag.Parse() // Call the openDB() helper function (see below) to create the connection pool, // passing in the config struct. If this returns an error, we log it and exit the // application immediately. db, err := openDB(cfg) if err != nil { logger.Fatal(err) } // Defer a call to db.Close() so that the connection pool is closed before the // main() function exits. defer func() { if closeErr := db.Close(); closeErr != nil && err == nil { err = closeErr } }() // Also log a message to say that the connection pool has been successfully // established. logger.Printf("database connection pool established") app := &application{ config: cfg, logger: logger, } // Use the httprouter instance returned by app.routes() as the server handler. srv := &http.Server{ Addr: fmt.Sprintf(":%d", cfg.port), Handler: app.routes(), IdleTimeout: time.Minute, ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, } logger.Printf("starting %s server on %s", cfg.env, srv.Addr) err = srv.ListenAndServe() logger.Fatal(err) } func openDB(cfg config) (*sql.DB, error) { // Use sql.Open() to create an empty connection pool, using the DSN from the config // struct. db, err := sql.Open("postgres", cfg.db.dsn) if err != nil { return nil, err } // Create a context with a 5-second timeout deadline. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // Use PingContext() to establish a new connection to the database, passing in the // context we created above as a parameter. If the connection couldn't be // established successfully within the 5 second deadline, then this will return an // error. err = db.PingContext(ctx) if err != nil { return nil, err } // Return the sql.DB connection pool. return db, nil }