diff --git a/api/api.go b/api/api.go
index 51916791c938a72a7816f4da3663ecdbe984b255..d846436d6292c032acb5d17fd1399e06c8db53b2 100644
--- a/api/api.go
+++ b/api/api.go
@@ -18,6 +18,7 @@ func initDB(dbPath string) (*gorm.DB, error) {
 	}
 
 	db.AutoMigrate(&Member{})
+	db.AutoMigrate(&Product{})
 	return db, err
 }
 
@@ -29,10 +30,17 @@ func Init(dbPath string, signKey string, r *mux.Router) error {
 
 	a := api{db, []byte(signKey)}
 	r.HandleFunc("/signin", a.SignIn).Methods("POST")
+
 	r.HandleFunc("/member", a.auth(a.ListMembers)).Methods("GET")
 	r.HandleFunc("/member", a.auth(a.AddMember)).Methods("POST")
 	r.HandleFunc("/member/{num:[0-9]+}", a.auth(a.GetMember)).Methods("GET")
 	r.HandleFunc("/member/{num:[0-9]+}", a.auth(a.UpdateMember)).Methods("PUT")
 	r.HandleFunc("/member/{num:[0-9]+}", a.auth(a.DeleteMember)).Methods("DELETE")
+
+	r.HandleFunc("/product", a.auth(a.ListProducts)).Methods("GET")
+	r.HandleFunc("/product", a.auth(a.AddProduct)).Methods("POST")
+	r.HandleFunc("/product/{code:[0-9]+}", a.auth(a.GetProduct)).Methods("GET")
+	r.HandleFunc("/product/{code:[0-9]+}", a.auth(a.UpdateProduct)).Methods("PUT")
+	r.HandleFunc("/product/{code:[0-9]+}", a.auth(a.DeleteProduct)).Methods("DELETE")
 	return nil
 }
diff --git a/api/member.go b/api/member.go
index 2408b33d02946d97ca1ddc771b591f3948bbcc25..60fa3e587e8966a5491dc7e73883c5c723d39cea 100644
--- a/api/member.go
+++ b/api/member.go
@@ -11,8 +11,8 @@ import (
 
 type Member struct {
 	gorm.Model `json:"-"`
-	Num        int    `json:"num",gorm:"unique;index"`
-	Name       string `json:"name",gorm:"unique;index"`
+	Num        int    `json:"num" gorm:"unique;index"`
+	Name       string `json:"name" gorm:"unique;index"`
 	Email      string `json:"email"`
 	Balance    int    `json:"balance"`
 	Role       string `json:"role"`
diff --git a/api/member_test.go b/api/member_test.go
index a0108f6708d1026ed59a090f0dece52005641b17..330d3734b5ab85b41a21a0d001ed7d124bfe2cb0 100644
--- a/api/member_test.go
+++ b/api/member_test.go
@@ -5,7 +5,7 @@ import (
 	"testing"
 )
 
-func TestAddList(t *testing.T) {
+func TestMemberAddList(t *testing.T) {
 	tapi := newTestAPI(t)
 	defer tapi.close()
 
@@ -36,7 +36,7 @@ func TestAddList(t *testing.T) {
 	}
 }
 
-func TestGetDelete(t *testing.T) {
+func TestMemberGetDelete(t *testing.T) {
 	tapi := newTestAPI(t)
 	defer tapi.close()
 
@@ -67,9 +67,14 @@ func TestGetDelete(t *testing.T) {
 	if resp.StatusCode != http.StatusOK {
 		t.Error("Can't find the member:", resp.Status)
 	}
+
+	resp = tapi.do("GET", "/member/10", nil, nil)
+	if resp.StatusCode != http.StatusNotFound {
+		t.Error("Expected not found after delete:", resp.Status, resp.Body)
+	}
 }
 
-func TestPut(t *testing.T) {
+func TestMemberUpdate(t *testing.T) {
 	tapi := newTestAPI(t)
 	defer tapi.close()
 
diff --git a/api/product.go b/api/product.go
new file mode 100644
index 0000000000000000000000000000000000000000..07a8ca3c62e4a70cf27ef5f78fa409dc3e737924
--- /dev/null
+++ b/api/product.go
@@ -0,0 +1,146 @@
+package api
+
+import (
+	"encoding/json"
+	"log"
+	"net/http"
+
+	"github.com/gorilla/mux"
+	"gorm.io/gorm"
+)
+
+type Product struct {
+	gorm.Model `json:"-"`
+	Code       int    `json:"code" gorm:"unique;index"`
+	Name       string `json:"name" gorm:"unique;index"`
+	Price      int    `json:"price"`
+	Stock      int    `json:"stock"`
+}
+
+func (a *api) AddProduct(w http.ResponseWriter, req *http.Request) {
+	var product Product
+	err := json.NewDecoder(req.Body).Decode(&product)
+	if err != nil {
+		log.Printf("Can't create product: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+	err = a.db.Create(&product).Error
+	if err != nil {
+		log.Printf("Can't create product: %v\n%v", err, product)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusCreated)
+	err = json.NewEncoder(w).Encode(product)
+	if err != nil {
+		log.Printf("Can't encode added product: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+}
+
+func (a *api) ListProducts(w http.ResponseWriter, req *http.Request) {
+	var products []Product
+	err := a.db.Find(&products).Error
+	if err != nil {
+		log.Printf("Can't list products: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusOK)
+	err = json.NewEncoder(w).Encode(products)
+	if err != nil {
+		log.Printf("Can't encode products: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+}
+
+func (a *api) GetProduct(w http.ResponseWriter, req *http.Request) {
+	vars := mux.Vars(req)
+	var product Product
+	err := a.db.Where("code = ?", vars["code"]).First(&product).Error
+	if err != nil {
+		if err.Error() == "record not found" {
+			w.WriteHeader(http.StatusNotFound)
+			return
+		}
+		log.Printf("Can't get product %s: %v", vars["code"], err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusOK)
+	err = json.NewEncoder(w).Encode(product)
+	if err != nil {
+		log.Printf("Can't encode product: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+}
+
+func (a *api) DeleteProduct(w http.ResponseWriter, req *http.Request) {
+	vars := mux.Vars(req)
+	err := a.db.Where("code = ?", vars["code"]).Delete(&Product{}).Error
+	if err != nil {
+		log.Printf("Can't delete product %s: %v", vars["code"], err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+	w.WriteHeader(http.StatusOK)
+}
+
+func (a *api) UpdateProduct(w http.ResponseWriter, req *http.Request) {
+	var product Product
+	err := json.NewDecoder(req.Body).Decode(&product)
+	if err != nil {
+		log.Printf("Can't decode memeber: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	vars := mux.Vars(req)
+	var dbProduct Product
+	err = a.db.Where("code = ?", vars["code"]).First(&dbProduct).Error
+	if err != nil {
+		if err.Error() == "record not found" {
+			w.WriteHeader(http.StatusNotFound)
+			return
+		}
+		log.Printf("Can't get product %s: %v", vars["code"], err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	if product.Code != 0 {
+		dbProduct.Code = product.Code
+	}
+	if product.Name != "" {
+		dbProduct.Name = product.Name
+	}
+	if product.Price >= 0 {
+		dbProduct.Price = product.Price
+	}
+	if product.Stock >= 0 {
+		dbProduct.Stock = product.Stock
+	}
+	err = a.db.Save(&dbProduct).Error
+	if err != nil {
+		log.Printf("Can't update product %s: %v %v", vars["code"], err, product)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusAccepted)
+	err = json.NewEncoder(w).Encode(dbProduct)
+	if err != nil {
+		log.Printf("Can't encode updated product: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+}
diff --git a/api/product_test.go b/api/product_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..66b7ddcc106fe1b2410547fecac9a576114633cb
--- /dev/null
+++ b/api/product_test.go
@@ -0,0 +1,106 @@
+package api
+
+import (
+	"net/http"
+	"testing"
+)
+
+func TestProductAddList(t *testing.T) {
+	tapi := newTestAPI(t)
+	defer tapi.close()
+
+	product := Product{
+		Code:  234,
+		Name:  "Aceite",
+		Price: 1700,
+		Stock: 10,
+	}
+	resp := tapi.do("POST", "/product", product, nil)
+	if resp.StatusCode != http.StatusCreated {
+		t.Fatal("Can't create product:", resp.Status)
+	}
+	var products []Product
+	resp = tapi.do("GET", "/product", nil, &products)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't get products:", resp.Status)
+	}
+
+	if len(products) != 1 {
+		t.Fatal("Wrong number of products", len(products), products)
+	}
+	if products[0].Name != "Aceite" {
+		t.Error("Wrong name:", products[0].Name)
+	}
+	if products[0].Price != 1700 {
+		t.Error("Wrong price:", products[0].Price)
+	}
+}
+
+func TestProductGetDelete(t *testing.T) {
+	tapi := newTestAPI(t)
+	defer tapi.close()
+
+	resp := tapi.do("GET", "/product/234", nil, nil)
+	if resp.StatusCode != http.StatusNotFound {
+		t.Error("Expected not found:", resp.Status, resp.Body)
+	}
+	product := Product{
+		Code:  234,
+		Name:  "Aceite",
+		Price: 1700,
+		Stock: 10,
+	}
+	resp = tapi.do("POST", "/product", product, nil)
+	if resp.StatusCode != http.StatusCreated {
+		t.Fatal("Can't create product:", resp.Status)
+	}
+
+	var gotProduct Product
+	resp = tapi.do("GET", "/product/234", nil, &gotProduct)
+	if resp.StatusCode != http.StatusOK {
+		t.Error("Can't find the product:", resp.Status)
+	}
+	if gotProduct.Code != 234 {
+		t.Error("Wrong product:", gotProduct.Code)
+	}
+	resp = tapi.do("DELETE", "/product/234", nil, nil)
+	if resp.StatusCode != http.StatusOK {
+		t.Error("Can't find the product:", resp.Status)
+	}
+
+	resp = tapi.do("GET", "/product/234", nil, nil)
+	if resp.StatusCode != http.StatusNotFound {
+		t.Error("Expected not found after delete:", resp.Status, resp.Body)
+	}
+}
+
+func TestProductUpdate(t *testing.T) {
+	tapi := newTestAPI(t)
+	defer tapi.close()
+
+	product := Product{
+		Code:  234,
+		Name:  "Aceite",
+		Price: 1700,
+		Stock: 10,
+	}
+	resp := tapi.do("POST", "/product", product, nil)
+	if resp.StatusCode != http.StatusCreated {
+		t.Fatal("Can't create product:", resp.Status)
+	}
+
+	product.Stock = product.Stock - 5
+	resp = tapi.do("PUT", "/product/234", product, nil)
+	if resp.StatusCode != http.StatusAccepted {
+		t.Fatal("Can't update product:", resp.Status)
+	}
+
+	var gotProduct Product
+	resp = tapi.do("GET", "/product/234", nil, &gotProduct)
+	if resp.StatusCode != http.StatusOK {
+		t.Error("Can't find the product:", resp.Status)
+	}
+	if gotProduct.Stock != 5 {
+		t.Error("Wrong sotck:", gotProduct)
+	}
+}