Create short URLs, resolve shortened URLs, and fetch all shortened URLs.
- Development
- Short URL API
- Base Config
- Resolve URL
- API: Create URL | Get URL | Get all URLs | Update URL | Delete URL
- Create
.env
file from.env.example
and populate with correct development variables go run cmd
Loosely based on Nic Jackson's microservice tutorials
- Definitions of the business data,
Link
, used in the application
type Link struct {
// Shortened URL result. Ex: https://ospk.org/bas12d21dc.
ShortURL string `json:"shortUrl" bson:"shortUrl"`
// Short Code used as the path of the short URL. Ex: bas12d21dc.
Code string `json:"code" bson:"code"`
// Optional custom short code passed when creating or updating the short URL.
CustomCode string `json:"customCode" bson:"customCode"`
// The URL where the short URL redirects.
OriginalUrl string `json:"originalUrl" bson:"originalUrl"`
// Count of times the short URL has been used.
TotalClicks int `json:"totalClicks" bson:"totalClicks"`
// Identifier of the entity that created the short URL.
CreatedBy string `json:"createdBy" bson:"createdBy"`
// DateTime the URL was created.
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
// DateTime the URL was last updated.
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
}
- HTTP route handlers that call the CRUD methods on the given
store
implementation.
func (s *ShortyService) createLink(w http.ResponseWriter, r *http.Request) {
// Parse JSON body into a link
linkInput.FromJSON(r.Body)
// Error handling omitted for brevity...
// Create and save the short link to the DB
newLink, err := s.store.SaveLink(r.Context(), linkInput)
// Send new link JSON
if err = newLink.ToJSON(w); err != nil {
http.Error(w, "Problem marshaling your short link", http.StatusInternalServerError)
}
}
- Contains
LinkStore
interface to be implemented in data access layer(s).
type LinkStore interface {
SaveLink(ctx context.Context, newLink shorty.Link) (shorty.Link, error)
FindLink(ctx context.Context, code string) (shorty.Link, error)
FindAllLinks(ctx context.Context) (shorty.Links, error)
UpdateLink(ctx context.Context, code string, toUpdate shorty.Link) (shorty.Link, error)
DeleteLink(ctx context.Context, code string) (int, error)
CheckCodeInUse(ctx context.Context, code string) (bool, error)
IncrementTotalClicks(ctx context.Context, code string) (int, error)
}
- Data access layer, implemented for MongoDB
func (i *Store) SaveLink(ctx context.Context, newLink shorty.Link) (shorty.Link, error) {
coll := i.Client.Database(i.DBName).Collection(i.LinksCollName)
_, err := coll.InsertOne(ctx, newLink)
if err != nil {
return shorty.Link{}, fmt.Errorf("insertOne: %v", err)
}
return newLink, nil
}
- Data access layer implemented for an in-memory store for simpler API testing
func (i *Store) SaveLink(ctx context.Context, newLink shorty.Link) (shorty.Link, error) {
i.lock.Lock()
defer i.lock.Unlock()
i.Store[newLink.Code] = newLink
return newLink, nil
}
- Entrypoint in to the Cloud function
- All packages tied together here:
func init() {
functions.HTTP("ServeShorty", NewMux().ServeHTTP)
}
var store shorty.ShortyStore
func NewMux() *http.ServeMux {
mongoURI := os.Getenv("MONGO_URI")
store, err := mongodb.NewStore(mongodb.StoreOpts{URI: mongoURI})
if err != nil {
panic(err)
}
baseURL := os.Getenv("HOST_BASE_URL")
apiKey := os.Getenv("API_KEY")
service := handlers.NewAPIService(store, baseURL, apiKey)
return handlers.NewServer(service)
}
If you already have a local MongoDB server running, you can use it for the tests.
$ go test
The tests can alternatively use docker to build and start a MongoDB service for you. Note: This will take a bit longer (30-60s).
$ go test --tags=integration
You can view your test coverage in a browser:
$ go-acc --covermode=count -o=coverage.out $(go list ./...)
$ go tool cover -html=coverage.out
API key
is required on all requests, not including GET /:code
to resolve the short URL.
- Base URL:
https://ospk.org
- Headers: { "key": "API_KEY" }
GET /:code
Response: 301 permanent redirect
POST /api/urls
Headers: key=$API_KEY
Body: { "originalUrl": "https://..." }
Response: "https://ospk.org/:code"
Request body properties
Key | Type | Required | Description |
---|---|---|---|
originalUrl | string |
true |
Original URL |
customCode | string |
Custom endpoint - Defaults to code |
|
createdBy | string |
User or bot that created the link |
Fetch short URL object
GET /api/urls/:code
Headers: key=$API_KEY
Example Response:
- See Short URL Properties for more details
{
"code": "a1b2c3d4e5",
"customCode": "a1b2c3d4e5",
"shortUrl": "https://ospk.org/a1b2c3d4e5",
"originalUrl": "https://oparationspark.org/infoSession",
"totalClicks": 0,
"createdBy": "user name",
"createdAt": "2022-10-21T03:17:15.400Z",
"updatedAt": "2022-10-21T03:17:15.400Z"
}
GET /api/urls
Headers: key=$API_KEY
Example Response:
- See Short URL Properties for more details
[
{"..."},
{
"code": "a1b2c3d4e5",
"customCode": "signup",
"shortUrl": "https://ospk.org/signup",
"originalUrl": "https://oparationspark.org/infoSession",
"totalClicks": 0,
"createdBy": "user name",
"createdAt": "2022-10-21T03:17:15.400Z",
"updatedAt": "2022-10-21T03:17:15.400Z"
},
{"..."}
]
PUT /api/urls/:code
Request body properties
Key | Type | Description |
---|---|---|
originalUrl | string |
Original URL |
customCode | string |
Custom endpoint - Defaults to code |
createdBy | string |
User or bot that created the link |
Example Request Body:
- See Short URL Properties for more details
{
"customCode": "info",
"originalUrl": "https://oparationspark.org/info-session",
"createdBy": "User Name"
}
Example Response:
- See Short URL Properties for more details
{
"code": "a1b2c3d4e5",
"customCode": "a1b2c3d4e5",
"shortUrl": "https://ospk.org/a1b2c3d4e5",
"originalUrl": "https://oparationspark.org/info-session",
"totalClicks": 0,
"createdBy": "User Name",
"createdAt": "2022-10-21T03:17:15.400Z",
"updatedAt": "2022-10-21T03:17:15.400Z"
}
DELETE /api/urls/:code
Headers: key=$API_KEY
Response Status: 200 | 404
Key | Type | Edit | Description |
---|---|---|---|
code | string |
randomized 10 character code | |
customCode | string |
true |
custom endpoint - Defaults to code |
shortUrl | string |
short url | |
originalUrl | string |
true |
Full URL originally provided |
createdBy | string |
true |
User or bot that created the link |
totalClicks | number |
Total clicks (Allows duplicates) | |
createdAt | Date |
Date created | |
updatedAt | Date |
Date last modified |